| author | |
| committer | |
| log | 0ff175b69ef806f421820d33dade7a8163fe3f16 |
| tree | 3ca6325f7c0de9632dc94fee68b868e9c31d0c31 |
| parent | c84f0f49d692e08c235ff939d4322fe723fe2823 |
| parent | c9619d7086a4aa61b1bd69faff4ce58a7f9c811c |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35428104 files changed, 19998 insertions(+), 13649 deletions(-)
build.zig+36-27| ... | ... | @@ -16,6 +16,8 @@ const IoMode = enum { threaded, evented }; |
| 16 | 16 | const ValueInterpretMode = enum { direct, by_name }; |
| 17 | 17 | |
| 18 | 18 | pub fn build(b: *std.Build) !void { |
| 19 | const arena = b.graph.arena; | |
| 20 | ||
| 19 | 21 | const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false; |
| 20 | 22 | const target = b.standardTargetOptions(.{ |
| 21 | 23 | .default_target = .{ |
| ... | ... | @@ -35,7 +37,7 @@ pub fn build(b: *std.Build) !void { |
| 35 | 37 | const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false; |
| 36 | 38 | const enable_superhtml = b.option(bool, "enable-superhtml", "Check langref output HTML validity") orelse false; |
| 37 | 39 | |
| 38 | const langref_file = generateLangRef(b); | |
| 40 | const langref_file = try generateLangRef(b); | |
| 39 | 41 | const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html"); |
| 40 | 42 | const check_langref = superHtmlCheck(b, langref_file); |
| 41 | 43 | if (enable_superhtml) install_langref.step.dependOn(check_langref); |
| ... | ... | @@ -208,7 +210,8 @@ pub fn build(b: *std.Build) !void { |
| 208 | 210 | .single_threaded = single_threaded, |
| 209 | 211 | }); |
| 210 | 212 | exe.pie = pie; |
| 211 | exe.entitlements = entitlements; | |
| 213 | // https://codeberg.org/ziglang/zig/issues/32173 | |
| 214 | exe.entitlements = if (entitlements) |p| .{ .cwd_relative = p } else null; | |
| 212 | 215 | exe.use_new_linker = b.option(bool, "new-linker", "Use the new linker"); |
| 213 | 216 | |
| 214 | 217 | const use_llvm = b.option(bool, "use-llvm", "Use the llvm backend"); |
| ... | ... | @@ -261,7 +264,7 @@ pub fn build(b: *std.Build) !void { |
| 261 | 264 | var code: u8 = undefined; |
| 262 | 265 | const git_describe_untrimmed = b.runAllowFail(&[_][]const u8{ |
| 263 | 266 | "git", |
| 264 | "-C", b.build_root.path orelse ".", // affects the --git-dir argument | |
| 267 | "-C", b.fmt("{f}", .{b.root}), // affects the --git-dir argument | |
| 265 | 268 | "--git-dir", ".git", // affected by the -C argument |
| 266 | 269 | "describe", "--match", "*.*.*", // |
| 267 | 270 | "--tags", "--abbrev=9", |
| ... | ... | @@ -307,7 +310,7 @@ pub fn build(b: *std.Build) !void { |
| 307 | 310 | }, |
| 308 | 311 | } |
| 309 | 312 | }; |
| 310 | const version = try b.allocator.dupeSentinel(u8, version_slice, 0); | |
| 313 | const version = try arena.dupeSentinel(u8, version_slice, 0); | |
| 311 | 314 | exe_options.addOption([:0]const u8, "version", version); |
| 312 | 315 | |
| 313 | 316 | if (enable_llvm) { |
| ... | ... | @@ -315,7 +318,7 @@ pub fn build(b: *std.Build) !void { |
| 315 | 318 | const io = b.graph.io; |
| 316 | 319 | const cwd: Io.Dir = .cwd(); |
| 317 | 320 | if (findConfigH(b, config_h_path_option)) |config_h_path| { |
| 318 | const file_contents = cwd.readFileAlloc(io, config_h_path, b.allocator, .limited(max_config_h_bytes)) catch unreachable; | |
| 321 | const file_contents = cwd.readFileAlloc(io, config_h_path, arena, .limited(max_config_h_bytes)) catch unreachable; | |
| 319 | 322 | break :blk parseConfigH(b, file_contents); |
| 320 | 323 | } else { |
| 321 | 324 | std.log.warn("config.h could not be located automatically. Consider providing it explicitly via \"-Dconfig_h\"", .{}); |
| ... | ... | @@ -424,8 +427,8 @@ pub fn build(b: *std.Build) !void { |
| 424 | 427 | else |
| 425 | 428 | null; |
| 426 | 429 | |
| 427 | const fmt_include_paths = &.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" }; | |
| 428 | const fmt_exclude_paths = &.{ "test/cases", "test/behavior/zon" }; | |
| 430 | const fmt_include_paths = b.pathList(&.{ "lib", "src", "test", "tools", "build.zig", "build.zig.zon" }); | |
| 431 | const fmt_exclude_paths = b.pathList(&.{ "test/cases", "test/behavior/zon" }); | |
| 429 | 432 | const do_fmt = b.addFmt(.{ |
| 430 | 433 | .paths = fmt_include_paths, |
| 431 | 434 | .exclude_paths = fmt_exclude_paths, |
| ... | ... | @@ -975,11 +978,12 @@ fn addCxxKnownPath( |
| 975 | 978 | errtxt: ?[]const u8, |
| 976 | 979 | need_cpp_includes: bool, |
| 977 | 980 | ) !void { |
| 978 | if (!std.process.can_spawn) | |
| 979 | return error.RequiredLibraryNotFound; | |
| 981 | if (!std.process.can_spawn) return error.RequiredLibraryNotFound; | |
| 982 | ||
| 983 | const arena = b.graph.arena; | |
| 980 | 984 | |
| 981 | 985 | const path_padded = run: { |
| 982 | var args = std.array_list.Managed([]const u8).init(b.allocator); | |
| 986 | var args = std.array_list.Managed([]const u8).init(arena); | |
| 983 | 987 | try args.append(ctx.cxx_compiler); |
| 984 | 988 | var it = std.mem.tokenizeAny(u8, ctx.cxx_compiler_arg1, &std.ascii.whitespace); |
| 985 | 989 | while (it.next()) |arg| try args.append(arg); |
| ... | ... | @@ -1048,6 +1052,7 @@ const CMakeConfig = struct { |
| 1048 | 1052 | const max_config_h_bytes = 1 * 1024 * 1024; |
| 1049 | 1053 | |
| 1050 | 1054 | fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { |
| 1055 | const arena = b.graph.arena; | |
| 1051 | 1056 | const io = b.graph.io; |
| 1052 | 1057 | const cwd: Io.Dir = .cwd(); |
| 1053 | 1058 | |
| ... | ... | @@ -1072,7 +1077,7 @@ fn findConfigH(b: *std.Build, config_h_path_option: ?[]const u8) ?[]const u8 { |
| 1072 | 1077 | if (config_h_or_err) |*file| { |
| 1073 | 1078 | file.close(io); |
| 1074 | 1079 | return fs.path.join( |
| 1075 | b.allocator, | |
| 1080 | arena, | |
| 1076 | 1081 | &[_][]const u8{ check_dir, "config.h" }, |
| 1077 | 1082 | ) catch unreachable; |
| 1078 | 1083 | } else |e| switch (e) { |
| ... | ... | @@ -1197,7 +1202,8 @@ fn parseConfigH(b: *std.Build, config_h_text: []const u8) ?CMakeConfig { |
| 1197 | 1202 | } |
| 1198 | 1203 | |
| 1199 | 1204 | fn toNativePathSep(b: *std.Build, s: []const u8) []u8 { |
| 1200 | const duplicated = b.allocator.dupe(u8, s) catch unreachable; | |
| 1205 | const arena = b.graph.arena; | |
| 1206 | const duplicated = arena.dupe(u8, s) catch unreachable; | |
| 1201 | 1207 | for (duplicated) |*byte| switch (byte.*) { |
| 1202 | 1208 | '/' => byte.* = fs.path.sep, |
| 1203 | 1209 | else => {}, |
| ... | ... | @@ -1486,8 +1492,9 @@ const llvm_libs_xtensa = [_][]const u8{ |
| 1486 | 1492 | "LLVMXtensaInfo", |
| 1487 | 1493 | }; |
| 1488 | 1494 | |
| 1489 | fn generateLangRef(b: *std.Build) std.Build.LazyPath { | |
| 1495 | fn generateLangRef(b: *std.Build) !std.Build.LazyPath { | |
| 1490 | 1496 | const io = b.graph.io; |
| 1497 | const arena = b.graph.arena; | |
| 1491 | 1498 | |
| 1492 | 1499 | const doctest_exe = b.addExecutable(.{ |
| 1493 | 1500 | .name = "doctest", |
| ... | ... | @@ -1498,11 +1505,10 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1498 | 1505 | }), |
| 1499 | 1506 | }); |
| 1500 | 1507 | |
| 1501 | var dir = b.build_root.handle.openDir(io, "doc/langref", .{ .iterate = true }) catch |err| { | |
| 1502 | std.debug.panic("unable to open '{f}doc/langref' directory: {s}", .{ | |
| 1503 | b.build_root, @errorName(err), | |
| 1504 | }); | |
| 1505 | }; | |
| 1508 | const langref_path = try b.root.join(arena, "doc/langref"); | |
| 1509 | ||
| 1510 | var dir = langref_path.root_dir.handle.openDir(io, langref_path.sub_path, .{ .iterate = true }) catch |err| | |
| 1511 | std.debug.panic("unable to open directory {f}: {t}", .{ langref_path, err }); | |
| 1506 | 1512 | defer dir.close(io); |
| 1507 | 1513 | |
| 1508 | 1514 | var wf = b.addWriteFiles(); |
| ... | ... | @@ -1515,17 +1521,20 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { |
| 1515 | 1521 | |
| 1516 | 1522 | const out_basename = b.fmt("{s}.out", .{std.fs.path.stem(entry.name)}); |
| 1517 | 1523 | const cmd = b.addRunArtifact(doctest_exe); |
| 1518 | cmd.addArgs(&.{ | |
| 1519 | "--zig", b.graph.zig_exe, | |
| 1520 | // TODO: enhance doctest to use "--listen=-" rather than operating | |
| 1521 | // in a temporary directory | |
| 1522 | "--cache-root", b.cache_root.path orelse ".", | |
| 1523 | }); | |
| 1524 | cmd.addArgs(&.{ "--zig-lib-dir", b.fmt("{f}", .{b.graph.zig_lib_directory}) }); | |
| 1525 | cmd.addArgs(&.{"-i"}); | |
| 1524 | ||
| 1525 | cmd.addArg("--zig"); | |
| 1526 | cmd.addFileArg(.zig_exe); | |
| 1527 | ||
| 1528 | cmd.addArg("--cache-root"); | |
| 1529 | cmd.addDirectoryArg(.cache_root); | |
| 1530 | ||
| 1531 | cmd.addArg("--zig-lib-dir"); | |
| 1532 | cmd.addDirectoryArg(.zig_lib); | |
| 1533 | ||
| 1534 | cmd.addArg("-i"); | |
| 1526 | 1535 | cmd.addFileArg(b.path(b.fmt("doc/langref/{s}", .{entry.name}))); |
| 1527 | 1536 | |
| 1528 | cmd.addArgs(&.{"-o"}); | |
| 1537 | cmd.addArg("-o"); | |
| 1529 | 1538 | _ = wf.addCopyFile(cmd.addOutputFileArg(out_basename), out_basename); |
| 1530 | 1539 | } |
| 1531 | 1540 |
ci/aarch64-freebsd-release.sh-1| ... | ... | @@ -59,7 +59,6 @@ stage3-release/bin/zig build \ |
| 59 | 59 | -Duse-zig-libcxx \ |
| 60 | 60 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 61 | 61 | |
| 62 | # diff returns an error code if the files differ. | |
| 63 | 62 | echo "If the following command fails, it means nondeterminism has been" |
| 64 | 63 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 65 | 64 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/aarch64-linux-release.sh-1| ... | ... | @@ -64,7 +64,6 @@ stage3-release/bin/zig build \ |
| 64 | 64 | -Duse-zig-libcxx \ |
| 65 | 65 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 66 | 66 | |
| 67 | # diff returns an error code if the files differ. | |
| 68 | 67 | echo "If the following command fails, it means nondeterminism has been" |
| 69 | 68 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 70 | 69 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/aarch64-macos-release.sh-1| ... | ... | @@ -73,7 +73,6 @@ stage3-release/bin/zig build \ |
| 73 | 73 | -Duse-zig-libcxx \ |
| 74 | 74 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 75 | 75 | |
| 76 | # diff returns an error code if the files differ. | |
| 77 | 76 | echo "If the following command fails, it means nondeterminism has been" |
| 78 | 77 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 79 | 78 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/aarch64-netbsd-release.sh-1| ... | ... | @@ -59,7 +59,6 @@ stage3-release/bin/zig build \ |
| 59 | 59 | -Duse-zig-libcxx \ |
| 60 | 60 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 61 | 61 | |
| 62 | # diff returns an error code if the files differ. | |
| 63 | 62 | echo "If the following command fails, it means nondeterminism has been" |
| 64 | 63 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 65 | 64 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/loongarch64-linux-release.sh-1| ... | ... | @@ -61,7 +61,6 @@ stage3-release/bin/zig build \ |
| 61 | 61 | -Duse-zig-libcxx \ |
| 62 | 62 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 63 | 63 | |
| 64 | # diff returns an error code if the files differ. | |
| 65 | 64 | echo "If the following command fails, it means nondeterminism has been" |
| 66 | 65 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 67 | 66 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/powerpc64le-linux-release.sh-1| ... | ... | @@ -63,7 +63,6 @@ stage3-release/bin/zig build \ |
| 63 | 63 | -Duse-zig-libcxx \ |
| 64 | 64 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 65 | 65 | |
| 66 | # diff returns an error code if the files differ. | |
| 67 | 66 | echo "If the following command fails, it means nondeterminism has been" |
| 68 | 67 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 69 | 68 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/s390x-linux-release.sh-1| ... | ... | @@ -62,7 +62,6 @@ stage3-release/bin/zig build \ |
| 62 | 62 | -Duse-zig-libcxx \ |
| 63 | 63 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 64 | 64 | |
| 65 | # diff returns an error code if the files differ. | |
| 66 | 65 | echo "If the following command fails, it means nondeterminism has been" |
| 67 | 66 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 68 | 67 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/x86_64-freebsd-release.sh-1| ... | ... | @@ -69,7 +69,6 @@ stage3-release/bin/zig build \ |
| 69 | 69 | -Duse-zig-libcxx \ |
| 70 | 70 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 71 | 71 | |
| 72 | # diff returns an error code if the files differ. | |
| 73 | 72 | echo "If the following command fails, it means nondeterminism has been" |
| 74 | 73 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 75 | 74 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/x86_64-linux-debug-llvm.sh+1| ... | ... | @@ -49,6 +49,7 @@ stage3-debug/bin/zig build \ |
| 49 | 49 | -Dno-lib |
| 50 | 50 | |
| 51 | 51 | stage3-debug/bin/zig build test docs \ |
| 52 | --maker-opt=Debug \ | |
| 52 | 53 | --maxrss ${ZSF_MAX_RSS:-0} \ |
| 53 | 54 | -Dlldb=$HOME/deps/lldb-zig/Debug-e0a42bb34/bin/lldb \ |
| 54 | 55 | -Dlibc-test-path=$HOME/deps/libc-test-f2bac77 \ |
ci/x86_64-linux-release.sh-1| ... | ... | @@ -85,7 +85,6 @@ stage3-release/bin/zig build \ |
| 85 | 85 | -Duse-zig-libcxx \ |
| 86 | 86 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 87 | 87 | |
| 88 | # diff returns an error code if the files differ. | |
| 89 | 88 | echo "If the following command fails, it means nondeterminism has been" |
| 90 | 89 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 91 | 90 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/x86_64-netbsd-release.sh-1| ... | ... | @@ -63,7 +63,6 @@ stage3-release/bin/zig build \ |
| 63 | 63 | -Duse-zig-libcxx \ |
| 64 | 64 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 65 | 65 | |
| 66 | # diff returns an error code if the files differ. | |
| 67 | 66 | echo "If the following command fails, it means nondeterminism has been" |
| 68 | 67 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 69 | 68 | diff stage3-release/bin/zig stage4-release/bin/zig |
ci/x86_64-openbsd-release.sh-1| ... | ... | @@ -64,7 +64,6 @@ stage3-release/bin/zig build \ |
| 64 | 64 | -Duse-zig-libcxx \ |
| 65 | 65 | -Dversion-string="$(stage3-release/bin/zig version)" |
| 66 | 66 | |
| 67 | # diff returns an error code if the files differ. | |
| 68 | 67 | echo "If the following command fails, it means nondeterminism has been" |
| 69 | 68 | echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." |
| 70 | 69 | diff stage3-release/bin/zig stage4-release/bin/zig |
lib/compiler/Maker.zig created+2051| ... | ... | @@ -0,0 +1,2051 @@ |
| 1 | const Maker = @This(); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Allocator = std.mem.Allocator; | |
| 6 | const Cache = std.Build.Cache; | |
| 7 | const Configuration = std.Build.Configuration; | |
| 8 | const File = std.Io.File; | |
| 9 | const Io = std.Io; | |
| 10 | const Dir = std.Io.Dir; | |
| 11 | const Path = std.Build.Cache.Path; | |
| 12 | const Writer = std.Io.Writer; | |
| 13 | const assert = std.debug.assert; | |
| 14 | const fatal = std.process.fatal; | |
| 15 | const fmt = std.fmt; | |
| 16 | const log = std.log; | |
| 17 | const mem = std.mem; | |
| 18 | const process = std.process; | |
| 19 | ||
| 20 | const Fuzz = @import("Maker/Fuzz.zig"); | |
| 21 | const Graph = @import("Maker/Graph.zig"); | |
| 22 | const Step = @import("Maker/Step.zig"); | |
| 23 | const Watch = @import("Maker/Watch.zig"); | |
| 24 | const WebServer = @import("Maker/WebServer.zig"); | |
| 25 | const ScannedConfig = @import("Maker/ScannedConfig.zig"); | |
| 26 | const PkgConfig = @import("Maker/PkgConfig.zig"); | |
| 27 | ||
| 28 | pub const std_options: std.Options = .{ | |
| 29 | .side_channels_mitigations = .none, | |
| 30 | .http_disable_tls = true, | |
| 31 | }; | |
| 32 | ||
| 33 | gpa: Allocator, | |
| 34 | graph: *Graph, | |
| 35 | install_paths: InstallPaths, | |
| 36 | scanned_config: *const ScannedConfig, | |
| 37 | steps: []Step, | |
| 38 | generated_files: []Path, | |
| 39 | run_args: ?[]const []const u8, | |
| 40 | ||
| 41 | available_rss: usize, | |
| 42 | max_rss_is_default: bool, | |
| 43 | max_rss_mutex: Io.Mutex, | |
| 44 | skip_oom_steps: bool, | |
| 45 | unit_test_timeout_ns: ?u64, | |
| 46 | watch: bool, | |
| 47 | web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn, | |
| 48 | /// Allocated into `gpa`. | |
| 49 | memory_blocked_steps: std.ArrayList(Configuration.Step.Index), | |
| 50 | /// Allocated into `gpa`. | |
| 51 | step_stack: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), | |
| 52 | pkg_config: PkgConfig, | |
| 53 | ||
| 54 | error_style: ErrorStyle, | |
| 55 | multiline_errors: MultilineErrors, | |
| 56 | summary: Summary, | |
| 57 | ||
| 58 | pub fn main(init: process.Init.Minimal) !void { | |
| 59 | // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not | |
| 60 | // always the case. So, we do need a true gpa for some things. | |
| 61 | var safe_gpa_state: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); | |
| 62 | defer _ = safe_gpa_state.deinit(); | |
| 63 | const gpa = safe_gpa_state.allocator(); | |
| 64 | ||
| 65 | var threaded: std.Io.Threaded = .init(gpa, .{ | |
| 66 | .environ = init.environ, | |
| 67 | .argv0 = .init(init.args), | |
| 68 | }); | |
| 69 | defer threaded.deinit(); | |
| 70 | const io = threaded.io(); | |
| 71 | ||
| 72 | // ...but we'll back our arena by `std.heap.page_allocator` for efficiency. | |
| 73 | var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator); | |
| 74 | defer arena_instance.deinit(); | |
| 75 | defer if (debugMakerLeaks()) log.debug("used {Bi} of arena", .{arena_instance.queryCapacity()}); | |
| 76 | const arena = arena_instance.allocator(); | |
| 77 | ||
| 78 | const args = try init.args.toSlice(arena); | |
| 79 | ||
| 80 | // skip my own exe name | |
| 81 | var arg_idx: usize = 1; | |
| 82 | ||
| 83 | const zig_exe = expectArgOrFatal(args, &arg_idx, "--zig"); | |
| 84 | const zig_lib_dir = expectArgOrFatal(args, &arg_idx, "--zig-lib-dir"); | |
| 85 | const build_root = expectArgOrFatal(args, &arg_idx, "--build-root"); | |
| 86 | const local_cache_root = expectArgOrFatal(args, &arg_idx, "--local-cache"); | |
| 87 | const global_cache_root = expectArgOrFatal(args, &arg_idx, "--global-cache"); | |
| 88 | const configure_path = expectArgOrFatal(args, &arg_idx, "--configuration"); | |
| 89 | ||
| 90 | const cwd: Dir = .cwd(); | |
| 91 | ||
| 92 | const zig_lib_directory: Cache.Directory = .{ | |
| 93 | .path = zig_lib_dir, | |
| 94 | .handle = try cwd.openDir(io, zig_lib_dir, .{}), | |
| 95 | }; | |
| 96 | ||
| 97 | const build_root_directory: Cache.Directory = .{ | |
| 98 | .path = build_root, | |
| 99 | .handle = try cwd.openDir(io, build_root, .{}), | |
| 100 | }; | |
| 101 | ||
| 102 | const local_cache_directory: Cache.Directory = .{ | |
| 103 | .path = local_cache_root, | |
| 104 | .handle = try cwd.createDirPathOpen(io, local_cache_root, .{}), | |
| 105 | }; | |
| 106 | ||
| 107 | const global_cache_directory: Cache.Directory = .{ | |
| 108 | .path = global_cache_root, | |
| 109 | .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}), | |
| 110 | }; | |
| 111 | ||
| 112 | var graph: Graph = .{ | |
| 113 | .io = io, | |
| 114 | .arena = arena, | |
| 115 | .cache = .{ | |
| 116 | .io = io, | |
| 117 | .gpa = gpa, | |
| 118 | .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), | |
| 119 | .cwd = try process.currentPathAlloc(io, arena), | |
| 120 | }, | |
| 121 | .zig_exe = zig_exe, | |
| 122 | .environ_map = try init.environ.createMap(arena), | |
| 123 | .global_cache_root = global_cache_directory, | |
| 124 | .local_cache_root = local_cache_directory, | |
| 125 | .zig_lib_directory = zig_lib_directory, | |
| 126 | .build_root_directory = build_root_directory, | |
| 127 | }; | |
| 128 | ||
| 129 | graph.cache.addPrefix(.{ .path = null, .handle = cwd }); | |
| 130 | graph.cache.addPrefix(build_root_directory); | |
| 131 | graph.cache.addPrefix(local_cache_directory); | |
| 132 | graph.cache.addPrefix(global_cache_directory); | |
| 133 | graph.cache.hash.addBytes(builtin.zig_version_string); | |
| 134 | ||
| 135 | var step_names: std.ArrayList([]const u8) = .empty; | |
| 136 | var help_menu = false; | |
| 137 | var steps_menu = false; | |
| 138 | var print_configuration = false; | |
| 139 | var override_install_prefix: ?[]const u8 = null; | |
| 140 | var override_lib_dir: ?[]const u8 = null; | |
| 141 | var override_bin_dir: ?[]const u8 = null; | |
| 142 | var override_include_dir: ?[]const u8 = null; | |
| 143 | var error_style: ErrorStyle = .verbose; | |
| 144 | var multiline_errors: MultilineErrors = .indent; | |
| 145 | var summary: ?Summary = null; | |
| 146 | var max_rss: u64 = 0; | |
| 147 | var skip_oom_steps = false; | |
| 148 | var test_timeout_ns: ?u64 = null; | |
| 149 | var color: Color = .auto; | |
| 150 | var watch = false; | |
| 151 | var fuzz: ?Fuzz.Mode = null; | |
| 152 | var debounce_interval_ms: u16 = 50; | |
| 153 | var webui_listen: ?Io.net.IpAddress = null; | |
| 154 | var debug_pkg_config = false; | |
| 155 | var run_args: ?[]const []const u8 = null; | |
| 156 | ||
| 157 | if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| { | |
| 158 | if (std.meta.stringToEnum(ErrorStyle, str)) |style| { | |
| 159 | error_style = style; | |
| 160 | } | |
| 161 | } | |
| 162 | ||
| 163 | if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| { | |
| 164 | if (std.meta.stringToEnum(MultilineErrors, str)) |style| { | |
| 165 | multiline_errors = style; | |
| 166 | } | |
| 167 | } | |
| 168 | ||
| 169 | while (nextArg(args, &arg_idx)) |arg| { | |
| 170 | if (mem.startsWith(u8, arg, "-")) { | |
| 171 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { | |
| 172 | help_menu = true; | |
| 173 | } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) { | |
| 174 | steps_menu = true; | |
| 175 | } else if (mem.eql(u8, arg, "--print-configuration")) { | |
| 176 | print_configuration = true; | |
| 177 | } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) { | |
| 178 | override_install_prefix = nextArgOrFatal(args, &arg_idx); | |
| 179 | } else if (mem.eql(u8, arg, "--prefix-lib-dir")) { | |
| 180 | override_lib_dir = nextArgOrFatal(args, &arg_idx); | |
| 181 | } else if (mem.eql(u8, arg, "--prefix-exe-dir")) { | |
| 182 | override_bin_dir = nextArgOrFatal(args, &arg_idx); | |
| 183 | } else if (mem.eql(u8, arg, "--prefix-include-dir")) { | |
| 184 | override_include_dir = nextArgOrFatal(args, &arg_idx); | |
| 185 | } else if (mem.eql(u8, arg, "--sysroot")) { | |
| 186 | graph.sysroot = nextArgOrFatal(args, &arg_idx); | |
| 187 | } else if (mem.eql(u8, arg, "--maxrss")) { | |
| 188 | const max_rss_text = nextArgOrFatal(args, &arg_idx); | |
| 189 | max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| | |
| 190 | fatal("invalid byte size {q}: {t}", .{ max_rss_text, err }); | |
| 191 | } else if (mem.eql(u8, arg, "--skip-oom-steps")) { | |
| 192 | skip_oom_steps = true; | |
| 193 | } else if (mem.eql(u8, arg, "--test-timeout")) { | |
| 194 | const units: []const struct { []const u8, u64 } = &.{ | |
| 195 | .{ "ns", 1 }, | |
| 196 | .{ "nanosecond", 1 }, | |
| 197 | .{ "us", std.time.ns_per_us }, | |
| 198 | .{ "microsecond", std.time.ns_per_us }, | |
| 199 | .{ "ms", std.time.ns_per_ms }, | |
| 200 | .{ "millisecond", std.time.ns_per_ms }, | |
| 201 | .{ "s", std.time.ns_per_s }, | |
| 202 | .{ "second", std.time.ns_per_s }, | |
| 203 | .{ "m", std.time.ns_per_min }, | |
| 204 | .{ "minute", std.time.ns_per_min }, | |
| 205 | .{ "h", std.time.ns_per_hour }, | |
| 206 | .{ "hour", std.time.ns_per_hour }, | |
| 207 | }; | |
| 208 | const timeout_str = nextArgOrFatal(args, &arg_idx); | |
| 209 | const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal( | |
| 210 | "invalid timeout {q}: expected unit (ns, us, ms, s, m, h)", | |
| 211 | .{timeout_str}, | |
| 212 | ); | |
| 213 | const num_str = timeout_str[0 .. num_end_idx + 1]; | |
| 214 | const unit_str = timeout_str[num_end_idx + 1 ..]; | |
| 215 | const unit_factor: f64 = for (units) |unit_and_factor| { | |
| 216 | if (std.mem.eql(u8, unit_str, unit_and_factor[0])) { | |
| 217 | break @floatFromInt(unit_and_factor[1]); | |
| 218 | } | |
| 219 | } else fatal( | |
| 220 | "invalid timeout {q}: invalid unit {q} (expected ns, us, ms, s, m, h)", | |
| 221 | .{ timeout_str, unit_str }, | |
| 222 | ); | |
| 223 | const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal( | |
| 224 | "invalid timeout {q}: invalid number {q} ({t})", | |
| 225 | .{ timeout_str, num_str, err }, | |
| 226 | ); | |
| 227 | test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed); | |
| 228 | } else if (mem.eql(u8, arg, "--search-prefix")) { | |
| 229 | try graph.search_prefixes.append(arena, nextArgOrFatal(args, &arg_idx)); | |
| 230 | } else if (mem.eql(u8, arg, "--libc")) { | |
| 231 | graph.libc_file = nextArgOrFatal(args, &arg_idx); | |
| 232 | } else if (mem.eql(u8, arg, "--color")) { | |
| 233 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 234 | fatalWithHint("expected [auto|on|off] after {q}", .{arg}); | |
| 235 | color = std.meta.stringToEnum(Color, next_arg) orelse { | |
| 236 | fatalWithHint("expected [auto|on|off] after {q}, found {q}", .{ | |
| 237 | arg, next_arg, | |
| 238 | }); | |
| 239 | }; | |
| 240 | } else if (mem.eql(u8, arg, "--error-style")) { | |
| 241 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 242 | fatalWithHint("expected style after {q}", .{arg}); | |
| 243 | error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse { | |
| 244 | fatalWithHint("expected style after {q}, found {q}", .{ arg, next_arg }); | |
| 245 | }; | |
| 246 | } else if (mem.eql(u8, arg, "--multiline-errors")) { | |
| 247 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 248 | fatalWithHint("expected style after {q}", .{arg}); | |
| 249 | multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse { | |
| 250 | fatalWithHint("expected style after {q}, found {q}", .{ arg, next_arg }); | |
| 251 | }; | |
| 252 | } else if (mem.eql(u8, arg, "--summary")) { | |
| 253 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 254 | fatalWithHint("expected [all|new|failures|line|none] after {q}", .{arg}); | |
| 255 | summary = std.meta.stringToEnum(Summary, next_arg) orelse { | |
| 256 | fatalWithHint("expected [all|new|failures|line|none] after {q}, found {q}", .{ | |
| 257 | arg, next_arg, | |
| 258 | }); | |
| 259 | }; | |
| 260 | } else if (mem.eql(u8, arg, "--seed")) { | |
| 261 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 262 | fatalWithHint("expected u32 after {q}", .{arg}); | |
| 263 | graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { | |
| 264 | fatal("unable to parse seed {q} as unsigned 32-bit integer: {t}", .{ next_arg, err }); | |
| 265 | }; | |
| 266 | } else if (mem.eql(u8, arg, "--build-id")) { | |
| 267 | graph.build_id = .fast; | |
| 268 | } else if (mem.cutPrefix(u8, arg, "--build-id=")) |style| { | |
| 269 | graph.build_id = std.zig.BuildId.parse(style) catch |err| | |
| 270 | fatal("unable to parse --build-id style {q}: {t}", .{ style, err }); | |
| 271 | } else if (mem.eql(u8, arg, "--debounce")) { | |
| 272 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 273 | fatalWithHint("expected u16 after {q}", .{arg}); | |
| 274 | debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| { | |
| 275 | fatal("unable to parse debounce interval {q} as unsigned 16-bit integer: {t}", .{ | |
| 276 | next_arg, err, | |
| 277 | }); | |
| 278 | }; | |
| 279 | } else if (mem.eql(u8, arg, "--webui")) { | |
| 280 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; | |
| 281 | } else if (mem.startsWith(u8, arg, "--webui=")) { | |
| 282 | const addr_str = arg["--webui=".len..]; | |
| 283 | if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{}); | |
| 284 | webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| { | |
| 285 | fatal("invalid web UI address {q}: {t}", .{ addr_str, err }); | |
| 286 | }; | |
| 287 | } else if (mem.eql(u8, arg, "--debug-log")) { | |
| 288 | const next_arg = nextArgOrFatal(args, &arg_idx); | |
| 289 | try graph.debug_log_scopes.append(arena, next_arg); | |
| 290 | } else if (mem.eql(u8, arg, "--debug-compile-errors")) { | |
| 291 | graph.debug_compile_errors = true; | |
| 292 | } else if (mem.eql(u8, arg, "--debug-incremental")) { | |
| 293 | graph.debug_incremental = true; | |
| 294 | } else if (mem.eql(u8, arg, "--debug-pkg-config")) { | |
| 295 | debug_pkg_config = true; | |
| 296 | } else if (mem.eql(u8, arg, "--debug-rt")) { | |
| 297 | graph.debug_compiler_runtime_libs = .Debug; | |
| 298 | } else if (mem.cutPrefix(u8, arg, "--debug-rt=")) |rest| { | |
| 299 | graph.debug_compiler_runtime_libs = std.meta.stringToEnum(std.builtin.OptimizeMode, rest) orelse | |
| 300 | fatal("unrecognized optimization mode: {s}", .{rest}); | |
| 301 | } else if (is_debug_mode and mem.eql(u8, arg, "--debug-maker-leaks")) { | |
| 302 | debug_maker_leaks = true; | |
| 303 | } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) { | |
| 304 | // --glibc-runtimes was the old name of the flag; kept for compatibility for now. | |
| 305 | graph.libc_runtimes_dir = nextArgOrFatal(args, &arg_idx); | |
| 306 | } else if (mem.eql(u8, arg, "--verbose")) { | |
| 307 | graph.verbose = true; | |
| 308 | } else if (mem.eql(u8, arg, "--verbose-air")) { | |
| 309 | graph.verbose_air = true; | |
| 310 | } else if (mem.eql(u8, arg, "--verbose-cc")) { | |
| 311 | graph.verbose_cc = true; | |
| 312 | } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { | |
| 313 | graph.verbose_llvm_ir = true; | |
| 314 | } else if (mem.eql(u8, arg, "--watch")) { | |
| 315 | watch = true; | |
| 316 | } else if (mem.eql(u8, arg, "--time-report")) { | |
| 317 | graph.time_report = true; | |
| 318 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; | |
| 319 | } else if (mem.eql(u8, arg, "--fuzz")) { | |
| 320 | fuzz = .{ .forever = undefined }; | |
| 321 | graph.fuzzing = true; | |
| 322 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; | |
| 323 | } else if (mem.startsWith(u8, arg, "--fuzz=")) { | |
| 324 | const value = arg["--fuzz=".len..]; | |
| 325 | if (value.len == 0) fatal("missing argument to --fuzz", .{}); | |
| 326 | ||
| 327 | const unit: u8 = value[value.len - 1]; | |
| 328 | const digits = switch (unit) { | |
| 329 | '0'...'9' => value, | |
| 330 | 'K', 'M', 'G' => value[0 .. value.len - 1], | |
| 331 | else => fatal( | |
| 332 | "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]", | |
| 333 | .{}, | |
| 334 | ), | |
| 335 | }; | |
| 336 | ||
| 337 | const amount = std.fmt.parseInt(u64, digits, 10) catch { | |
| 338 | fatal( | |
| 339 | "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]", | |
| 340 | .{}, | |
| 341 | ); | |
| 342 | }; | |
| 343 | ||
| 344 | const normalized_amount = std.math.mul(u64, amount, switch (unit) { | |
| 345 | else => unreachable, | |
| 346 | '0'...'9' => 1, | |
| 347 | 'K' => 1000, | |
| 348 | 'M' => 1_000_000, | |
| 349 | 'G' => 1_000_000_000, | |
| 350 | }) catch fatal("fuzzing limit amount overflows u64", .{}); | |
| 351 | ||
| 352 | fuzz = .{ | |
| 353 | .limit = .{ | |
| 354 | .amount = normalized_amount, | |
| 355 | }, | |
| 356 | }; | |
| 357 | graph.fuzzing = true; | |
| 358 | } else if (mem.eql(u8, arg, "-fincremental")) { | |
| 359 | graph.incremental = true; | |
| 360 | } else if (mem.eql(u8, arg, "-fno-incremental")) { | |
| 361 | graph.incremental = false; | |
| 362 | } else if (mem.eql(u8, arg, "-fwine")) { | |
| 363 | graph.enable_wine = true; | |
| 364 | } else if (mem.eql(u8, arg, "-fno-wine")) { | |
| 365 | graph.enable_wine = false; | |
| 366 | } else if (mem.eql(u8, arg, "-fqemu")) { | |
| 367 | graph.enable_qemu = true; | |
| 368 | } else if (mem.eql(u8, arg, "-fno-qemu")) { | |
| 369 | graph.enable_qemu = false; | |
| 370 | } else if (mem.eql(u8, arg, "-fwasmtime")) { | |
| 371 | graph.enable_wasmtime = true; | |
| 372 | } else if (mem.eql(u8, arg, "-fno-wasmtime")) { | |
| 373 | graph.enable_wasmtime = false; | |
| 374 | } else if (mem.eql(u8, arg, "-frosetta")) { | |
| 375 | graph.enable_rosetta = true; | |
| 376 | } else if (mem.eql(u8, arg, "-fno-rosetta")) { | |
| 377 | graph.enable_rosetta = false; | |
| 378 | } else if (mem.eql(u8, arg, "-fdarling")) { | |
| 379 | graph.enable_darling = true; | |
| 380 | } else if (mem.eql(u8, arg, "-fno-darling")) { | |
| 381 | graph.enable_darling = false; | |
| 382 | } else if (mem.eql(u8, arg, "-fallow-so-scripts")) { | |
| 383 | graph.allow_so_scripts = true; | |
| 384 | } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) { | |
| 385 | graph.allow_so_scripts = false; | |
| 386 | } else if (mem.eql(u8, arg, "-freference-trace")) { | |
| 387 | graph.reference_trace = 256; | |
| 388 | } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| { | |
| 389 | graph.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| | |
| 390 | fatal("unable to parse reference_trace count {q}: {t}", .{ num, err }); | |
| 391 | } else if (mem.eql(u8, arg, "-fno-reference-trace")) { | |
| 392 | graph.reference_trace = null; | |
| 393 | } else if (mem.eql(u8, arg, "--error-limit")) { | |
| 394 | const next_arg = nextArgOrFatal(args, &arg_idx); | |
| 395 | graph.error_limit = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| | |
| 396 | fatal("unable to parse error limit {q}: {t}", .{ next_arg, err }); | |
| 397 | } else if (mem.cutPrefix(u8, arg, "-j")) |text| { | |
| 398 | const n = std.fmt.parseUnsigned(u32, text, 10) catch |err| | |
| 399 | fatal("unable to parse jobs count {q}: {t}", .{ text, err }); | |
| 400 | if (n < 1) fatal("number of jobs must be at least 1", .{}); | |
| 401 | threaded.setAsyncLimit(.limited(n)); | |
| 402 | graph.max_jobs = n; | |
| 403 | } else if (mem.eql(u8, arg, "--")) { | |
| 404 | run_args = argsRest(args, arg_idx); | |
| 405 | break; | |
| 406 | } else { | |
| 407 | fatalWithHint("unrecognized argument: {s}", .{arg}); | |
| 408 | } | |
| 409 | } else { | |
| 410 | try step_names.append(arena, arg); | |
| 411 | } | |
| 412 | } | |
| 413 | ||
| 414 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map); | |
| 415 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map); | |
| 416 | ||
| 417 | graph.stderr_mode = switch (color) { | |
| 418 | .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), | |
| 419 | .on => .escape_codes, | |
| 420 | .off => .no_color, | |
| 421 | }; | |
| 422 | ||
| 423 | const scanned_config: ScannedConfig = sc: { | |
| 424 | const configuration = c: { | |
| 425 | var file = cwd.openFile(io, configure_path, .{}) catch |err| | |
| 426 | fatal("failed to open configuration file {s}: {t}", .{ configure_path, err }); | |
| 427 | defer file.close(io); | |
| 428 | break :c Configuration.loadFile(arena, io, file) catch |err| | |
| 429 | fatal("failed to load configuration file {s}: {t}", .{ configure_path, err }); | |
| 430 | }; | |
| 431 | // Technically if the configuration is marked as poisoned, we could | |
| 432 | // already delete the file now, but we leave it around in case the | |
| 433 | // maker process fails or crashes and it's helpful to be able to repeat | |
| 434 | // execution of the command line or otherwise inspect the configuration file. | |
| 435 | const c = &configuration; | |
| 436 | var top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index) = .empty; | |
| 437 | for (configuration.steps, 0..) |*conf_step, step_index_usize| { | |
| 438 | if (conf_step.owner != .root) continue; | |
| 439 | const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize); | |
| 440 | const flags = conf_step.flags(c); | |
| 441 | switch (flags.tag) { | |
| 442 | .top_level => { | |
| 443 | const name = step_index.ptr(c).name.slice(c); | |
| 444 | try top_level_steps.put(arena, name, step_index); | |
| 445 | }, | |
| 446 | else => {}, | |
| 447 | } | |
| 448 | } | |
| 449 | for (c.search_prefixes) |search_prefix| { | |
| 450 | try graph.search_prefixes.append(arena, search_prefix.slice(c)); | |
| 451 | } | |
| 452 | break :sc .{ | |
| 453 | .configuration = configuration, | |
| 454 | .top_level_steps = top_level_steps, | |
| 455 | .path = configure_path, | |
| 456 | }; | |
| 457 | }; | |
| 458 | ||
| 459 | if (help_menu) { | |
| 460 | var w = initStdoutWriter(io); | |
| 461 | scanned_config.printUsage(&graph, w) catch |err| switch (err) { | |
| 462 | error.WriteFailed => return stdout_writer_allocation.err.?, | |
| 463 | else => |e| return e, | |
| 464 | }; | |
| 465 | w.flush() catch return stdout_writer_allocation.err.?; | |
| 466 | return cleanExit(io, &scanned_config); | |
| 467 | } else if (steps_menu) { | |
| 468 | var w = initStdoutWriter(io); | |
| 469 | scanned_config.printSteps(&graph, w) catch |err| switch (err) { | |
| 470 | error.WriteFailed => return stdout_writer_allocation.err.?, | |
| 471 | else => |e| return e, | |
| 472 | }; | |
| 473 | w.flush() catch return stdout_writer_allocation.err.?; | |
| 474 | return cleanExit(io, &scanned_config); | |
| 475 | } else if (print_configuration) { | |
| 476 | var w = initStdoutWriter(io); | |
| 477 | scanned_config.print(w) catch return stdout_writer_allocation.err.?; | |
| 478 | w.flush() catch return stdout_writer_allocation.err.?; | |
| 479 | return cleanExit(io, &scanned_config); | |
| 480 | } | |
| 481 | ||
| 482 | if (webui_listen != null) { | |
| 483 | if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{}); | |
| 484 | if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{}); | |
| 485 | } | |
| 486 | ||
| 487 | const main_progress_node = std.Progress.start(io, .{ | |
| 488 | .disable_printing = (color == .off), | |
| 489 | }); | |
| 490 | defer main_progress_node.end(); | |
| 491 | ||
| 492 | const install_prefix_path: Path = if (graph.environ_map.get("DESTDIR")) |dest_dir| .{ | |
| 493 | .root_dir = .cwd(), | |
| 494 | .sub_path = try Dir.path.join(arena, &.{ dest_dir, override_install_prefix orelse "/usr" }), | |
| 495 | } else if (override_install_prefix) |cwd_relative| .{ | |
| 496 | .root_dir = .cwd(), | |
| 497 | .sub_path = cwd_relative, | |
| 498 | } else .{ | |
| 499 | .root_dir = build_root_directory, | |
| 500 | .sub_path = "zig-out", | |
| 501 | }; | |
| 502 | ||
| 503 | const install_lib_path: Path = if (override_lib_dir) |cwd_relative| .{ | |
| 504 | .root_dir = .cwd(), | |
| 505 | .sub_path = cwd_relative, | |
| 506 | } else try install_prefix_path.join(arena, "lib"); | |
| 507 | ||
| 508 | const install_bin_path: Path = if (override_bin_dir) |cwd_relative| .{ | |
| 509 | .root_dir = .cwd(), | |
| 510 | .sub_path = cwd_relative, | |
| 511 | } else try install_prefix_path.join(arena, "bin"); | |
| 512 | ||
| 513 | const install_include_path: Path = if (override_include_dir) |cwd_relative| .{ | |
| 514 | .root_dir = .cwd(), | |
| 515 | .sub_path = cwd_relative, | |
| 516 | } else try install_prefix_path.join(arena, "include"); | |
| 517 | ||
| 518 | var maker: Maker = .{ | |
| 519 | .gpa = gpa, | |
| 520 | .graph = &graph, | |
| 521 | .scanned_config = &scanned_config, | |
| 522 | .install_paths = .{ | |
| 523 | .prefix = install_prefix_path, | |
| 524 | .lib = install_lib_path, | |
| 525 | .bin = install_bin_path, | |
| 526 | .include = install_include_path, | |
| 527 | }, | |
| 528 | ||
| 529 | .steps = try arena.alloc(Step, scanned_config.configuration.steps.len), | |
| 530 | .generated_files = try arena.alloc(Path, scanned_config.configuration.generated_files_len), | |
| 531 | .run_args = run_args, | |
| 532 | ||
| 533 | .available_rss = max_rss, | |
| 534 | .max_rss_is_default = false, | |
| 535 | .max_rss_mutex = .init, | |
| 536 | .skip_oom_steps = skip_oom_steps, | |
| 537 | .unit_test_timeout_ns = test_timeout_ns, | |
| 538 | ||
| 539 | .watch = watch, | |
| 540 | .web_server = undefined, // set after `prepare` | |
| 541 | .memory_blocked_steps = .empty, | |
| 542 | .step_stack = .empty, | |
| 543 | .pkg_config = .{ .debug = debug_pkg_config }, | |
| 544 | ||
| 545 | .error_style = error_style, | |
| 546 | .multiline_errors = multiline_errors, | |
| 547 | .summary = summary orelse if (watch or webui_listen != null) .line else .failures, | |
| 548 | }; | |
| 549 | defer { | |
| 550 | maker.memory_blocked_steps.deinit(gpa); | |
| 551 | maker.step_stack.deinit(gpa); | |
| 552 | } | |
| 553 | ||
| 554 | if (maker.available_rss == 0) { | |
| 555 | maker.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64); | |
| 556 | maker.max_rss_is_default = true; | |
| 557 | } | |
| 558 | ||
| 559 | maker.prepare(step_names.items) catch |err| switch (err) { | |
| 560 | error.DependencyLoopDetected, error.InsufficientMemory => { | |
| 561 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; | |
| 562 | process.exit(1); | |
| 563 | }, | |
| 564 | else => |e| return e, | |
| 565 | }; | |
| 566 | ||
| 567 | var w: Watch = w: { | |
| 568 | if (!watch) break :w undefined; | |
| 569 | if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag}); | |
| 570 | break :w try .init(&maker); | |
| 571 | }; | |
| 572 | ||
| 573 | const now = Io.Clock.Timestamp.now(io, .awake); | |
| 574 | ||
| 575 | maker.web_server = if (webui_listen) |listen_address| ws: { | |
| 576 | if (builtin.single_threaded) unreachable; // `fatal` above | |
| 577 | break :ws .init(.{ | |
| 578 | .maker = &maker, | |
| 579 | .root_prog_node = main_progress_node, | |
| 580 | .listen_address = listen_address, | |
| 581 | .base_timestamp = now, | |
| 582 | }); | |
| 583 | } else null; | |
| 584 | ||
| 585 | if (maker.web_server) |*ws| { | |
| 586 | ws.start() catch |err| fatal("failed to start web server: {t}", .{err}); | |
| 587 | } | |
| 588 | ||
| 589 | rebuild: while (true) : (if (maker.error_style.clearOnUpdate()) { | |
| 590 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 591 | defer io.unlockStderr(); | |
| 592 | stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H") catch |err| switch (err) { | |
| 593 | error.WriteFailed => return stderr.file_writer.err.?, | |
| 594 | }; | |
| 595 | }) { | |
| 596 | if (maker.web_server) |*ws| ws.startBuild(); | |
| 597 | ||
| 598 | try maker.makeStepNames(step_names.items, main_progress_node, fuzz); | |
| 599 | ||
| 600 | if (maker.web_server) |*web_server| { | |
| 601 | if (fuzz) |mode| if (mode != .forever) fatal( | |
| 602 | "error: limited fuzzing is not implemented yet for --webui", | |
| 603 | .{}, | |
| 604 | ); | |
| 605 | ||
| 606 | web_server.finishBuild(.{ .fuzz = fuzz != null }); | |
| 607 | } | |
| 608 | ||
| 609 | if (maker.web_server) |*web_server| { | |
| 610 | const c = &scanned_config.configuration; | |
| 611 | assert(!watch); // fatal error after CLI parsing | |
| 612 | while (true) switch (try web_server.wait()) { | |
| 613 | .rebuild => { | |
| 614 | for (maker.step_stack.keys()) |step_index| { | |
| 615 | const step = maker.stepByIndex(step_index); | |
| 616 | step.state = .precheck_done; | |
| 617 | const deps = step_index.ptr(c).deps.slice(c); | |
| 618 | step.pending_deps = @intCast(deps.len); | |
| 619 | step.reset(&maker); | |
| 620 | } | |
| 621 | continue :rebuild; | |
| 622 | }, | |
| 623 | }; | |
| 624 | } | |
| 625 | ||
| 626 | if (!maker.watch) return; | |
| 627 | ||
| 628 | // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`. | |
| 629 | if (!Watch.have_impl) unreachable; | |
| 630 | ||
| 631 | try w.update(maker.step_stack.keys()); | |
| 632 | ||
| 633 | // Wait until a file system notification arrives. Read all such events | |
| 634 | // until the buffer is empty. Then wait for a debounce interval, resetting | |
| 635 | // if any more events come in. After the debounce interval has passed, | |
| 636 | // trigger a rebuild on all steps with modified inputs, as well as their | |
| 637 | // recursive dependants. | |
| 638 | var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined; | |
| 639 | const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{ | |
| 640 | w.dir_count, countSubProcesses(&maker), | |
| 641 | }) catch &caption_buf; | |
| 642 | var debouncing_node = main_progress_node.start(caption, 0); | |
| 643 | var in_debounce = false; | |
| 644 | while (true) switch (try w.wait(if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) { | |
| 645 | .timeout => { | |
| 646 | assert(in_debounce); | |
| 647 | debouncing_node.end(); | |
| 648 | markFailedStepsDirty(&maker); | |
| 649 | continue :rebuild; | |
| 650 | }, | |
| 651 | .dirty => if (!in_debounce) { | |
| 652 | in_debounce = true; | |
| 653 | debouncing_node.end(); | |
| 654 | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); | |
| 655 | }, | |
| 656 | .clean => {}, | |
| 657 | }; | |
| 658 | } | |
| 659 | } | |
| 660 | ||
| 661 | fn markFailedStepsDirty(maker: *Maker) void { | |
| 662 | const all_steps = maker.step_stack.keys(); | |
| 663 | ||
| 664 | for (all_steps) |step_index| { | |
| 665 | const step = maker.stepByIndex(step_index); | |
| 666 | switch (step.state) { | |
| 667 | .dependency_failure, .failure, .skipped => _ = maker.invalidateResult(step), | |
| 668 | else => continue, | |
| 669 | } | |
| 670 | } | |
| 671 | // Now that all dirty steps have been found, the remaining steps that | |
| 672 | // succeeded from last run shall be marked "cached". | |
| 673 | for (all_steps) |step_index| { | |
| 674 | const step = maker.stepByIndex(step_index); | |
| 675 | switch (step.state) { | |
| 676 | .success => step.result_cached = true, | |
| 677 | else => continue, | |
| 678 | } | |
| 679 | } | |
| 680 | } | |
| 681 | ||
| 682 | fn countSubProcesses(maker: *Maker) usize { | |
| 683 | const all_steps = maker.step_stack.keys(); | |
| 684 | var count: usize = 0; | |
| 685 | for (all_steps) |step_index| { | |
| 686 | const s = maker.stepByIndex(step_index); | |
| 687 | count += @intFromBool(s.getZigProcess() != null); | |
| 688 | } | |
| 689 | return count; | |
| 690 | } | |
| 691 | ||
| 692 | const InstallPaths = struct { | |
| 693 | prefix: Path, | |
| 694 | lib: Path, | |
| 695 | bin: Path, | |
| 696 | include: Path, | |
| 697 | }; | |
| 698 | ||
| 699 | pub fn stepByIndex(maker: *const Maker, i: Configuration.Step.Index) *Step { | |
| 700 | return &maker.steps[@intFromEnum(i)]; | |
| 701 | } | |
| 702 | ||
| 703 | fn prepare(maker: *Maker, step_names: []const []const u8) !void { | |
| 704 | const gpa = maker.gpa; | |
| 705 | const graph = maker.graph; | |
| 706 | const arena = graph.arena; | |
| 707 | const seed: u32 = graph.random_seed; | |
| 708 | const step_stack = &maker.step_stack; | |
| 709 | const c = &maker.scanned_config.configuration; | |
| 710 | ||
| 711 | for (maker.steps, 0..) |*step, step_index_usize| { | |
| 712 | const step_index: Configuration.Step.Index = @enumFromInt(step_index_usize); | |
| 713 | step.* = .{ .extended = .init(step_index.ptr(c).flags(c).tag) }; | |
| 714 | } | |
| 715 | ||
| 716 | if (step_names.len == 0) { | |
| 717 | try step_stack.put(gpa, c.default_step, {}); | |
| 718 | } else { | |
| 719 | try step_stack.ensureUnusedCapacity(gpa, step_names.len); | |
| 720 | for (0..step_names.len) |i| { | |
| 721 | const step_name = step_names[step_names.len - i - 1]; | |
| 722 | const s = maker.scanned_config.top_level_steps.get(step_name) orelse { | |
| 723 | log.info("to list available steps: zig build -l", .{}); | |
| 724 | fatal("no such step: {s}", .{step_name}); | |
| 725 | }; | |
| 726 | step_stack.putAssumeCapacity(s, {}); | |
| 727 | } | |
| 728 | } | |
| 729 | ||
| 730 | const starting_steps = try arena.dupe(Configuration.Step.Index, step_stack.keys()); | |
| 731 | ||
| 732 | var rng = std.Random.DefaultPrng.init(seed); | |
| 733 | const rand = rng.random(); | |
| 734 | rand.shuffle(Configuration.Step.Index, starting_steps); | |
| 735 | ||
| 736 | for (starting_steps) |s| { | |
| 737 | try constructGraphAndCheckForDependencyLoop(maker, s, &maker.step_stack, rand); | |
| 738 | } | |
| 739 | ||
| 740 | { | |
| 741 | // Check that we have enough memory to complete the build. | |
| 742 | var any_problems = false; | |
| 743 | var max_needed: usize = 0; | |
| 744 | for (step_stack.keys()) |step_index| { | |
| 745 | const make_step = maker.stepByIndex(step_index); | |
| 746 | const conf_step = step_index.ptr(c); | |
| 747 | const max_rss = conf_step.max_rss.toBytes(); | |
| 748 | if (max_rss == 0) continue; | |
| 749 | max_needed = @max(max_needed, max_rss); | |
| 750 | if (max_rss > maker.available_rss) { | |
| 751 | if (maker.skip_oom_steps) { | |
| 752 | make_step.state = .skipped_oom; | |
| 753 | for (make_step.dependants.items) |dependant| { | |
| 754 | maker.stepByIndex(dependant).pending_deps -= 1; | |
| 755 | } | |
| 756 | } else { | |
| 757 | log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ | |
| 758 | conf_step.owner.depPrefixSlice(c), | |
| 759 | conf_step.name.slice(c), | |
| 760 | max_rss, | |
| 761 | maker.available_rss, | |
| 762 | }); | |
| 763 | any_problems = true; | |
| 764 | } | |
| 765 | } | |
| 766 | } | |
| 767 | if (any_problems) { | |
| 768 | if (maker.max_rss_is_default) { | |
| 769 | std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{ | |
| 770 | max_needed, | |
| 771 | }); | |
| 772 | } | |
| 773 | return error.InsufficientMemory; | |
| 774 | } | |
| 775 | } | |
| 776 | } | |
| 777 | ||
| 778 | fn makeStepNames( | |
| 779 | maker: *Maker, | |
| 780 | step_names: []const []const u8, | |
| 781 | parent_prog_node: std.Progress.Node, | |
| 782 | fuzz: ?Fuzz.Mode, | |
| 783 | ) !void { | |
| 784 | const graph = maker.graph; | |
| 785 | const gpa = maker.gpa; | |
| 786 | const io = graph.io; | |
| 787 | const step_stack = &maker.step_stack; | |
| 788 | const top_level_steps = &maker.scanned_config.top_level_steps; | |
| 789 | const c = &maker.scanned_config.configuration; | |
| 790 | ||
| 791 | { | |
| 792 | // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, | |
| 793 | // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking | |
| 794 | // a step is initial when it actually became ready due to an earlier initial step. | |
| 795 | var initial_set: std.ArrayList(Configuration.Step.Index) = .empty; | |
| 796 | defer initial_set.deinit(gpa); | |
| 797 | try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); | |
| 798 | for (step_stack.keys()) |step_index| { | |
| 799 | const s = maker.stepByIndex(step_index); | |
| 800 | if (s.state == .precheck_done and s.pending_deps == 0) { | |
| 801 | initial_set.appendAssumeCapacity(step_index); | |
| 802 | } | |
| 803 | } | |
| 804 | ||
| 805 | const step_prog = parent_prog_node.start("steps", step_stack.count()); | |
| 806 | defer step_prog.end(); | |
| 807 | ||
| 808 | var group: Io.Group = .init; | |
| 809 | defer group.cancel(io); | |
| 810 | // Start working on all of the initial steps... | |
| 811 | for (initial_set.items) |step_index| try stepReady(maker, &group, step_index, step_prog); | |
| 812 | // ...and `makeStep` will trigger every other step when their last dependency finishes. | |
| 813 | try group.await(io); | |
| 814 | } | |
| 815 | ||
| 816 | assert(maker.memory_blocked_steps.items.len == 0); | |
| 817 | ||
| 818 | var test_pass_count: usize = 0; | |
| 819 | var test_skip_count: usize = 0; | |
| 820 | var test_fail_count: usize = 0; | |
| 821 | var test_crash_count: usize = 0; | |
| 822 | var test_timeout_count: usize = 0; | |
| 823 | ||
| 824 | var test_count: usize = 0; | |
| 825 | ||
| 826 | var success_count: usize = 0; | |
| 827 | var skipped_count: usize = 0; | |
| 828 | var failure_count: usize = 0; | |
| 829 | var pending_count: usize = 0; | |
| 830 | var total_compile_errors: usize = 0; | |
| 831 | ||
| 832 | var cleanup_task = io.async(cleanTmpFiles, .{ maker, step_stack.keys() }); | |
| 833 | defer cleanup_task.await(io); | |
| 834 | ||
| 835 | for (step_stack.keys()) |step_index| { | |
| 836 | const make_step = maker.stepByIndex(step_index); | |
| 837 | test_pass_count += make_step.test_results.passCount(); | |
| 838 | test_skip_count += make_step.test_results.skip_count; | |
| 839 | test_fail_count += make_step.test_results.fail_count; | |
| 840 | test_crash_count += make_step.test_results.crash_count; | |
| 841 | test_timeout_count += make_step.test_results.timeout_count; | |
| 842 | ||
| 843 | test_count += make_step.test_results.test_count; | |
| 844 | ||
| 845 | switch (make_step.state) { | |
| 846 | .precheck_unstarted => unreachable, | |
| 847 | .precheck_started => unreachable, | |
| 848 | .precheck_done => unreachable, | |
| 849 | .dependency_failure => pending_count += 1, | |
| 850 | .success => success_count += 1, | |
| 851 | .skipped, .skipped_oom => skipped_count += 1, | |
| 852 | .failure => { | |
| 853 | failure_count += 1; | |
| 854 | const compile_errors_len = make_step.result_error_bundle.errorMessageCount(); | |
| 855 | if (compile_errors_len > 0) { | |
| 856 | total_compile_errors += compile_errors_len; | |
| 857 | } | |
| 858 | }, | |
| 859 | } | |
| 860 | } | |
| 861 | ||
| 862 | if (fuzz) |mode| blk: { | |
| 863 | switch (builtin.os.tag) { | |
| 864 | // Current implementation depends on two things that need to be ported to Windows: | |
| 865 | // * Memory-mapping to share data between the fuzzer and build runner. | |
| 866 | // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving | |
| 867 | // many addresses to source locations). | |
| 868 | .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}), | |
| 869 | else => {}, | |
| 870 | } | |
| 871 | if (@bitSizeOf(usize) != 64) { | |
| 872 | // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, | |
| 873 | // being compatible with file system's u64 return value. This is not the case | |
| 874 | // on 32-bit platforms. | |
| 875 | // Affects or affected by issues #5185, #22523, and #22464. | |
| 876 | fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); | |
| 877 | } | |
| 878 | ||
| 879 | switch (mode) { | |
| 880 | .forever => break :blk, | |
| 881 | .limit => {}, | |
| 882 | } | |
| 883 | ||
| 884 | assert(mode == .limit); | |
| 885 | var f = Fuzz.init(maker, step_stack.keys(), parent_prog_node, mode) catch |err| | |
| 886 | fatal("failed to start fuzzer: {t}", .{err}); | |
| 887 | defer f.deinit(); | |
| 888 | ||
| 889 | f.start(); | |
| 890 | try f.waitAndPrintReport(); | |
| 891 | } | |
| 892 | ||
| 893 | // Every test has a state | |
| 894 | assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count); | |
| 895 | ||
| 896 | if (failure_count == 0) { | |
| 897 | std.Progress.setStatus(.success); | |
| 898 | } else { | |
| 899 | std.Progress.setStatus(.failure); | |
| 900 | } | |
| 901 | ||
| 902 | summary: { | |
| 903 | switch (maker.summary) { | |
| 904 | .all, .new, .line => {}, | |
| 905 | .failures => if (failure_count == 0) break :summary, | |
| 906 | .none => break :summary, | |
| 907 | } | |
| 908 | ||
| 909 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 910 | defer io.unlockStderr(); | |
| 911 | const t = stderr.terminal(); | |
| 912 | const w = &stderr.file_writer.interface; | |
| 913 | ||
| 914 | const total_count = success_count + failure_count + pending_count + skipped_count; | |
| 915 | t.setColor(.cyan) catch {}; | |
| 916 | t.setColor(.bold) catch {}; | |
| 917 | w.writeAll("Build Summary: ") catch {}; | |
| 918 | t.setColor(.reset) catch {}; | |
| 919 | w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; | |
| 920 | { | |
| 921 | t.setColor(.dim) catch {}; | |
| 922 | var first = true; | |
| 923 | if (skipped_count > 0) { | |
| 924 | w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; | |
| 925 | first = false; | |
| 926 | } | |
| 927 | if (failure_count > 0) { | |
| 928 | w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {}; | |
| 929 | first = false; | |
| 930 | } | |
| 931 | if (!first) w.writeByte(')') catch {}; | |
| 932 | t.setColor(.reset) catch {}; | |
| 933 | } | |
| 934 | ||
| 935 | if (test_count > 0) { | |
| 936 | w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; | |
| 937 | t.setColor(.dim) catch {}; | |
| 938 | var first = true; | |
| 939 | if (test_skip_count > 0) { | |
| 940 | w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; | |
| 941 | first = false; | |
| 942 | } | |
| 943 | if (test_fail_count > 0) { | |
| 944 | w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {}; | |
| 945 | first = false; | |
| 946 | } | |
| 947 | if (test_crash_count > 0) { | |
| 948 | w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {}; | |
| 949 | first = false; | |
| 950 | } | |
| 951 | if (test_timeout_count > 0) { | |
| 952 | w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {}; | |
| 953 | first = false; | |
| 954 | } | |
| 955 | if (!first) w.writeByte(')') catch {}; | |
| 956 | t.setColor(.reset) catch {}; | |
| 957 | } | |
| 958 | ||
| 959 | w.writeAll("\n") catch {}; | |
| 960 | ||
| 961 | if (maker.summary == .line) break :summary; | |
| 962 | ||
| 963 | // Print a fancy tree with build results. | |
| 964 | var step_stack_copy = try step_stack.clone(gpa); | |
| 965 | defer step_stack_copy.deinit(gpa); | |
| 966 | ||
| 967 | var print_node: PrintNode = .{ .parent = null }; | |
| 968 | if (step_names.len == 0) { | |
| 969 | print_node.last = true; | |
| 970 | printTreeStep(maker, c.default_step, t, &print_node, &step_stack_copy) catch |err| switch (err) { | |
| 971 | error.Canceled => |e| return e, | |
| 972 | else => {}, | |
| 973 | }; | |
| 974 | } else { | |
| 975 | const last_index = if (maker.summary == .all) top_level_steps.count() else blk: { | |
| 976 | var i: usize = step_names.len; | |
| 977 | while (i > 0) { | |
| 978 | i -= 1; | |
| 979 | const step_index = top_level_steps.get(step_names[i]).?; | |
| 980 | const step = maker.stepByIndex(step_index); | |
| 981 | const found = switch (maker.summary) { | |
| 982 | .all, .line, .none => unreachable, | |
| 983 | .failures => step.state != .success, | |
| 984 | .new => !step.result_cached, | |
| 985 | }; | |
| 986 | if (found) break :blk i; | |
| 987 | } | |
| 988 | break :blk top_level_steps.count(); | |
| 989 | }; | |
| 990 | for (step_names, 0..) |step_name, i| { | |
| 991 | const step_index = top_level_steps.get(step_name).?; | |
| 992 | print_node.last = i + 1 == last_index; | |
| 993 | printTreeStep(maker, step_index, t, &print_node, &step_stack_copy) catch |err| switch (err) { | |
| 994 | error.Canceled => |e| return e, | |
| 995 | else => {}, | |
| 996 | }; | |
| 997 | } | |
| 998 | } | |
| 999 | w.writeByte('\n') catch {}; | |
| 1000 | } | |
| 1001 | ||
| 1002 | if (maker.watch or maker.web_server != null) return; | |
| 1003 | ||
| 1004 | const code: u8 = code: { | |
| 1005 | if (failure_count == 0) break :code 0; // success | |
| 1006 | if (maker.error_style.verboseContext()) break :code 1; // failure; print build command | |
| 1007 | break :code 2; // failure; do not print build command | |
| 1008 | }; | |
| 1009 | if (code == 0) { | |
| 1010 | removePoisonedConfiguration(io, maker.scanned_config); | |
| 1011 | if (debugMakerLeaks()) return deinit(maker); | |
| 1012 | } | |
| 1013 | cleanup_task.await(io); // There is a defer above but an exit below. | |
| 1014 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; | |
| 1015 | process.exit(code); | |
| 1016 | } | |
| 1017 | ||
| 1018 | fn deinit(maker: *Maker) void { | |
| 1019 | const gpa = maker.gpa; | |
| 1020 | for (maker.steps) |*step| { | |
| 1021 | step.clearFailedCommand(gpa); | |
| 1022 | step.clearErrorBundle(gpa); | |
| 1023 | step.inputs.deinit(gpa); | |
| 1024 | } | |
| 1025 | } | |
| 1026 | ||
| 1027 | fn stepReady( | |
| 1028 | maker: *Maker, | |
| 1029 | group: *Io.Group, | |
| 1030 | step_index: Configuration.Step.Index, | |
| 1031 | root_prog_node: std.Progress.Node, | |
| 1032 | ) Io.Cancelable!void { | |
| 1033 | const graph = maker.graph; | |
| 1034 | const io = graph.io; | |
| 1035 | const c = &maker.scanned_config.configuration; | |
| 1036 | const max_rss = step_index.ptr(c).max_rss.toBytes(); | |
| 1037 | if (max_rss != 0) { | |
| 1038 | try maker.max_rss_mutex.lock(io); | |
| 1039 | defer maker.max_rss_mutex.unlock(io); | |
| 1040 | if (maker.available_rss < max_rss) { | |
| 1041 | // Running this step right now could possibly exceed the allotted RSS. | |
| 1042 | maker.memory_blocked_steps.append(maker.gpa, step_index) catch | |
| 1043 | @panic("TODO eliminate memory allocation here"); | |
| 1044 | return; | |
| 1045 | } | |
| 1046 | maker.available_rss -= max_rss; | |
| 1047 | } | |
| 1048 | group.async(io, makeStep, .{ maker, group, step_index, root_prog_node }); | |
| 1049 | } | |
| 1050 | ||
| 1051 | /// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready | |
| 1052 | /// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must | |
| 1053 | /// have already subtracted this value from `maker.available_rss`. This function will release the RSS | |
| 1054 | /// claim (i.e. add `s.max_rss` back into `maker.available_rss`) and queue any viable memory-blocked | |
| 1055 | /// steps after "make" completes for `s`. | |
| 1056 | fn makeStep( | |
| 1057 | maker: *Maker, | |
| 1058 | group: *Io.Group, | |
| 1059 | step_index: Configuration.Step.Index, | |
| 1060 | root_prog_node: std.Progress.Node, | |
| 1061 | ) Io.Cancelable!void { | |
| 1062 | const graph = maker.graph; | |
| 1063 | const io = graph.io; | |
| 1064 | const gpa = maker.gpa; | |
| 1065 | const c = &maker.scanned_config.configuration; | |
| 1066 | const conf_step = step_index.ptr(c); | |
| 1067 | const step_name = conf_step.name.slice(c); | |
| 1068 | const deps = conf_step.deps.slice(c); | |
| 1069 | const make_step = maker.stepByIndex(step_index); | |
| 1070 | ||
| 1071 | { | |
| 1072 | const step_prog_node = root_prog_node.start(step_name, 0); | |
| 1073 | defer step_prog_node.end(); | |
| 1074 | ||
| 1075 | if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .wip); | |
| 1076 | ||
| 1077 | const new_state: Step.State = for (deps) |dep_index| { | |
| 1078 | const dep_make_step = maker.stepByIndex(dep_index); | |
| 1079 | switch (@atomicLoad(Step.State, &dep_make_step.state, .monotonic)) { | |
| 1080 | .precheck_unstarted => unreachable, | |
| 1081 | .precheck_started => unreachable, | |
| 1082 | .precheck_done => unreachable, | |
| 1083 | ||
| 1084 | .failure, | |
| 1085 | .dependency_failure, | |
| 1086 | .skipped_oom, | |
| 1087 | => break .dependency_failure, | |
| 1088 | ||
| 1089 | .success, .skipped => {}, | |
| 1090 | } | |
| 1091 | } else if (Step.make(step_index, maker, step_prog_node)) state: { | |
| 1092 | break :state .success; | |
| 1093 | } else |err| switch (err) { | |
| 1094 | error.MakeFailed => .failure, | |
| 1095 | error.MakeSkipped => .skipped, | |
| 1096 | error.Canceled => |e| return e, | |
| 1097 | }; | |
| 1098 | ||
| 1099 | @atomicStore(Step.State, &make_step.state, new_state, .monotonic); | |
| 1100 | ||
| 1101 | switch (new_state) { | |
| 1102 | .precheck_unstarted => unreachable, | |
| 1103 | .precheck_started => unreachable, | |
| 1104 | .precheck_done => unreachable, | |
| 1105 | ||
| 1106 | .failure, | |
| 1107 | .dependency_failure, | |
| 1108 | .skipped_oom, | |
| 1109 | => { | |
| 1110 | if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .failure); | |
| 1111 | std.Progress.setStatus(.failure_working); | |
| 1112 | }, | |
| 1113 | ||
| 1114 | .success, | |
| 1115 | .skipped, | |
| 1116 | => { | |
| 1117 | if (maker.web_server) |*ws| ws.updateStepStatus(step_index, .success); | |
| 1118 | }, | |
| 1119 | } | |
| 1120 | } | |
| 1121 | ||
| 1122 | // No matter the result, we want to display error/warning messages. | |
| 1123 | if (make_step.result_error_bundle.errorMessageCount() > 0 or | |
| 1124 | make_step.result_error_msgs.items.len > 0 or | |
| 1125 | make_step.result_stderr.len > 0) | |
| 1126 | { | |
| 1127 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 1128 | defer io.unlockStderr(); | |
| 1129 | printErrorMessages(maker, step_index, .{}, stderr.terminal(), maker.error_style, maker.multiline_errors) catch |err| switch (err) { | |
| 1130 | error.Canceled => |e| return e, | |
| 1131 | error.WriteFailed => switch (stderr.file_writer.err.?) { | |
| 1132 | error.Canceled => |e| return e, | |
| 1133 | else => {}, | |
| 1134 | }, | |
| 1135 | else => {}, | |
| 1136 | }; | |
| 1137 | } | |
| 1138 | ||
| 1139 | const max_rss = conf_step.max_rss.toBytes(); | |
| 1140 | if (max_rss != 0) { | |
| 1141 | var dispatch_set: std.ArrayList(Configuration.Step.Index) = .empty; | |
| 1142 | defer dispatch_set.deinit(gpa); | |
| 1143 | ||
| 1144 | // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set` | |
| 1145 | // as a staging buffer to avoid recursing into `makeStep` while `maker.max_rss_mutex` is held. | |
| 1146 | { | |
| 1147 | try maker.max_rss_mutex.lock(io); | |
| 1148 | defer maker.max_rss_mutex.unlock(io); | |
| 1149 | maker.available_rss += max_rss; | |
| 1150 | dispatch_set.ensureUnusedCapacity(gpa, maker.memory_blocked_steps.items.len) catch | |
| 1151 | @panic("TODO eliminate memory allocation here"); | |
| 1152 | while (maker.memory_blocked_steps.getLast()) |candidate_index| { | |
| 1153 | const candidate_max_rss = candidate_index.ptr(c).max_rss.toBytes(); | |
| 1154 | if (maker.available_rss < candidate_max_rss) break; | |
| 1155 | assert(maker.memory_blocked_steps.pop() == candidate_index); | |
| 1156 | dispatch_set.appendAssumeCapacity(candidate_index); | |
| 1157 | } | |
| 1158 | } | |
| 1159 | for (dispatch_set.items) |candidate| { | |
| 1160 | group.async(io, makeStep, .{ maker, group, candidate, root_prog_node }); | |
| 1161 | } | |
| 1162 | } | |
| 1163 | ||
| 1164 | for (make_step.dependants.items) |dependant_index| { | |
| 1165 | const dependant = maker.stepByIndex(dependant_index); | |
| 1166 | // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0. | |
| 1167 | if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) { | |
| 1168 | try stepReady(maker, group, dependant_index, root_prog_node); | |
| 1169 | } | |
| 1170 | } | |
| 1171 | } | |
| 1172 | ||
| 1173 | fn printTreeStep( | |
| 1174 | maker: *Maker, | |
| 1175 | step_index: Configuration.Step.Index, | |
| 1176 | stderr: Io.Terminal, | |
| 1177 | parent_node: *PrintNode, | |
| 1178 | step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), | |
| 1179 | ) !void { | |
| 1180 | const writer = stderr.writer; | |
| 1181 | const first = step_stack.swapRemove(step_index); | |
| 1182 | const summary = maker.summary; | |
| 1183 | const c = &maker.scanned_config.configuration; | |
| 1184 | const conf_step = step_index.ptr(c); | |
| 1185 | const make_step = maker.stepByIndex(step_index); | |
| 1186 | const skip = switch (summary) { | |
| 1187 | .none, .line => unreachable, | |
| 1188 | .all => false, | |
| 1189 | .new => make_step.result_cached, | |
| 1190 | .failures => make_step.state == .success, | |
| 1191 | }; | |
| 1192 | if (skip) return; | |
| 1193 | try printPrefix(parent_node, stderr); | |
| 1194 | ||
| 1195 | if (parent_node.parent != null) { | |
| 1196 | if (parent_node.last) { | |
| 1197 | try printChildNodePrefix(stderr); | |
| 1198 | } else { | |
| 1199 | try writer.writeAll(switch (stderr.mode) { | |
| 1200 | .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ | |
| 1201 | else => "+- ", | |
| 1202 | }); | |
| 1203 | } | |
| 1204 | } | |
| 1205 | ||
| 1206 | if (!first) try stderr.setColor(.dim); | |
| 1207 | ||
| 1208 | // dep_prefix omitted here because it is redundant with the tree. | |
| 1209 | try writer.writeAll(conf_step.name.slice(c)); | |
| 1210 | ||
| 1211 | const deps = conf_step.deps.slice(c); | |
| 1212 | ||
| 1213 | if (first) { | |
| 1214 | try printStepStatus(maker, step_index, stderr); | |
| 1215 | ||
| 1216 | const last_index = if (summary == .all) deps.len -| 1 else blk: { | |
| 1217 | var i: usize = deps.len; | |
| 1218 | while (i > 0) { | |
| 1219 | i -= 1; | |
| 1220 | ||
| 1221 | const dep_index = deps[i]; | |
| 1222 | const dep = maker.stepByIndex(dep_index); | |
| 1223 | const found = switch (summary) { | |
| 1224 | .all, .line, .none => unreachable, | |
| 1225 | .failures => dep.state != .success, | |
| 1226 | .new => !dep.result_cached, | |
| 1227 | }; | |
| 1228 | if (found) break :blk i; | |
| 1229 | } | |
| 1230 | break :blk deps.len -| 1; | |
| 1231 | }; | |
| 1232 | for (deps, 0..) |dep, i| { | |
| 1233 | var print_node: PrintNode = .{ | |
| 1234 | .parent = parent_node, | |
| 1235 | .last = i == last_index, | |
| 1236 | }; | |
| 1237 | try printTreeStep(maker, dep, stderr, &print_node, step_stack); | |
| 1238 | } | |
| 1239 | } else { | |
| 1240 | if (deps.len == 0) { | |
| 1241 | try writer.writeAll(" (reused)\n"); | |
| 1242 | } else { | |
| 1243 | try writer.print(" (+{d} more reused dependencies)\n", .{deps.len}); | |
| 1244 | } | |
| 1245 | try stderr.setColor(.reset); | |
| 1246 | } | |
| 1247 | } | |
| 1248 | ||
| 1249 | fn printStepStatus(maker: *Maker, step_index: Configuration.Step.Index, stderr: Io.Terminal) !void { | |
| 1250 | const s = maker.stepByIndex(step_index); | |
| 1251 | const writer = stderr.writer; | |
| 1252 | switch (s.state) { | |
| 1253 | .precheck_unstarted => unreachable, | |
| 1254 | .precheck_started => unreachable, | |
| 1255 | .precheck_done => unreachable, | |
| 1256 | ||
| 1257 | .dependency_failure => { | |
| 1258 | try stderr.setColor(.dim); | |
| 1259 | try writer.writeAll(" transitive failure\n"); | |
| 1260 | try stderr.setColor(.reset); | |
| 1261 | }, | |
| 1262 | ||
| 1263 | .success => { | |
| 1264 | try stderr.setColor(.green); | |
| 1265 | if (s.result_cached) { | |
| 1266 | try writer.writeAll(" cached"); | |
| 1267 | } else if (s.test_results.test_count > 0) { | |
| 1268 | const pass_count = s.test_results.passCount(); | |
| 1269 | assert(s.test_results.test_count == pass_count + s.test_results.skip_count); | |
| 1270 | try writer.print(" {d} pass", .{pass_count}); | |
| 1271 | if (s.test_results.skip_count > 0) { | |
| 1272 | try stderr.setColor(.reset); | |
| 1273 | try writer.writeAll(", "); | |
| 1274 | try stderr.setColor(.yellow); | |
| 1275 | try writer.print("{d} skip", .{s.test_results.skip_count}); | |
| 1276 | } | |
| 1277 | try stderr.setColor(.reset); | |
| 1278 | try writer.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1279 | } else { | |
| 1280 | try writer.writeAll(" success"); | |
| 1281 | } | |
| 1282 | try stderr.setColor(.reset); | |
| 1283 | if (s.result_duration_ns) |ns| { | |
| 1284 | try stderr.setColor(.dim); | |
| 1285 | if (ns >= std.time.ns_per_min) { | |
| 1286 | try writer.print(" {d}m", .{ns / std.time.ns_per_min}); | |
| 1287 | } else if (ns >= std.time.ns_per_s) { | |
| 1288 | try writer.print(" {d}s", .{ns / std.time.ns_per_s}); | |
| 1289 | } else if (ns >= std.time.ns_per_ms) { | |
| 1290 | try writer.print(" {d}ms", .{ns / std.time.ns_per_ms}); | |
| 1291 | } else if (ns >= std.time.ns_per_us) { | |
| 1292 | try writer.print(" {d}us", .{ns / std.time.ns_per_us}); | |
| 1293 | } else { | |
| 1294 | try writer.print(" {d}ns", .{ns}); | |
| 1295 | } | |
| 1296 | try stderr.setColor(.reset); | |
| 1297 | } | |
| 1298 | if (s.result_peak_rss != 0) { | |
| 1299 | const rss = s.result_peak_rss; | |
| 1300 | try stderr.setColor(.dim); | |
| 1301 | if (rss >= 1000_000_000) { | |
| 1302 | try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); | |
| 1303 | } else if (rss >= 1000_000) { | |
| 1304 | try writer.print(" MaxRSS:{d}M", .{rss / 1000_000}); | |
| 1305 | } else if (rss >= 1000) { | |
| 1306 | try writer.print(" MaxRSS:{d}K", .{rss / 1000}); | |
| 1307 | } else { | |
| 1308 | try writer.print(" MaxRSS:{d}B", .{rss}); | |
| 1309 | } | |
| 1310 | try stderr.setColor(.reset); | |
| 1311 | } | |
| 1312 | try writer.writeAll("\n"); | |
| 1313 | }, | |
| 1314 | .skipped => { | |
| 1315 | try stderr.setColor(.yellow); | |
| 1316 | try writer.writeAll(" skipped\n"); | |
| 1317 | try stderr.setColor(.reset); | |
| 1318 | }, | |
| 1319 | .skipped_oom => { | |
| 1320 | const c = &maker.scanned_config.configuration; | |
| 1321 | const max_rss = step_index.ptr(c).max_rss.toBytes(); | |
| 1322 | try stderr.setColor(.yellow); | |
| 1323 | try writer.writeAll(" skipped (not enough memory)"); | |
| 1324 | try stderr.setColor(.dim); | |
| 1325 | try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{ | |
| 1326 | max_rss, maker.available_rss, | |
| 1327 | }); | |
| 1328 | try stderr.setColor(.reset); | |
| 1329 | }, | |
| 1330 | .failure => { | |
| 1331 | try printStepFailure(maker, step_index, stderr, false); | |
| 1332 | try stderr.setColor(.reset); | |
| 1333 | }, | |
| 1334 | } | |
| 1335 | } | |
| 1336 | ||
| 1337 | fn printStepFailure( | |
| 1338 | maker: *Maker, | |
| 1339 | step_index: Configuration.Step.Index, | |
| 1340 | stderr: Io.Terminal, | |
| 1341 | dim: bool, | |
| 1342 | ) !void { | |
| 1343 | const w = stderr.writer; | |
| 1344 | const s = maker.stepByIndex(step_index); | |
| 1345 | if (s.result_error_bundle.errorMessageCount() > 0) { | |
| 1346 | try stderr.setColor(.red); | |
| 1347 | try w.print(" {d} errors\n", .{ | |
| 1348 | s.result_error_bundle.errorMessageCount(), | |
| 1349 | }); | |
| 1350 | } else if (!s.test_results.isSuccess()) { | |
| 1351 | // These first values include all of the test "statuses". Every test is either passsed, | |
| 1352 | // skipped, failed, crashed, or timed out. | |
| 1353 | try stderr.setColor(.green); | |
| 1354 | try w.print(" {d} pass", .{s.test_results.passCount()}); | |
| 1355 | try stderr.setColor(.reset); | |
| 1356 | if (dim) try stderr.setColor(.dim); | |
| 1357 | if (s.test_results.skip_count > 0) { | |
| 1358 | try w.writeAll(", "); | |
| 1359 | try stderr.setColor(.yellow); | |
| 1360 | try w.print("{d} skip", .{s.test_results.skip_count}); | |
| 1361 | try stderr.setColor(.reset); | |
| 1362 | if (dim) try stderr.setColor(.dim); | |
| 1363 | } | |
| 1364 | if (s.test_results.fail_count > 0) { | |
| 1365 | try w.writeAll(", "); | |
| 1366 | try stderr.setColor(.red); | |
| 1367 | try w.print("{d} fail", .{s.test_results.fail_count}); | |
| 1368 | try stderr.setColor(.reset); | |
| 1369 | if (dim) try stderr.setColor(.dim); | |
| 1370 | } | |
| 1371 | if (s.test_results.crash_count > 0) { | |
| 1372 | try w.writeAll(", "); | |
| 1373 | try stderr.setColor(.red); | |
| 1374 | try w.print("{d} crash", .{s.test_results.crash_count}); | |
| 1375 | try stderr.setColor(.reset); | |
| 1376 | if (dim) try stderr.setColor(.dim); | |
| 1377 | } | |
| 1378 | if (s.test_results.timeout_count > 0) { | |
| 1379 | try w.writeAll(", "); | |
| 1380 | try stderr.setColor(.red); | |
| 1381 | try w.print("{d} timeout", .{s.test_results.timeout_count}); | |
| 1382 | try stderr.setColor(.reset); | |
| 1383 | if (dim) try stderr.setColor(.dim); | |
| 1384 | } | |
| 1385 | try w.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1386 | ||
| 1387 | // Memory leaks are intentionally written after the total, because is isn't a test *status*, | |
| 1388 | // but just a flag that any tests -- even passed ones -- can have. We also use a different | |
| 1389 | // separator, so it looks like: | |
| 1390 | // 2 pass, 1 skip, 2 fail (5 total); 2 leaks | |
| 1391 | if (s.test_results.leak_count > 0) { | |
| 1392 | try w.writeAll("; "); | |
| 1393 | try stderr.setColor(.red); | |
| 1394 | try w.print("{d} leaks", .{s.test_results.leak_count}); | |
| 1395 | try stderr.setColor(.reset); | |
| 1396 | if (dim) try stderr.setColor(.dim); | |
| 1397 | } | |
| 1398 | ||
| 1399 | // It's usually not helpful to know how many error logs there were because they tend to | |
| 1400 | // just come with other errors (e.g. crashes and leaks print stack traces, and clean | |
| 1401 | // failures print error traces). So only mention them if they're the only thing causing | |
| 1402 | // the failure. | |
| 1403 | const show_err_logs: bool = show: { | |
| 1404 | var alt_results = s.test_results; | |
| 1405 | alt_results.log_err_count = 0; | |
| 1406 | break :show alt_results.isSuccess(); | |
| 1407 | }; | |
| 1408 | if (show_err_logs) { | |
| 1409 | try w.writeAll("; "); | |
| 1410 | try stderr.setColor(.red); | |
| 1411 | try w.print("{d} error logs", .{s.test_results.log_err_count}); | |
| 1412 | try stderr.setColor(.reset); | |
| 1413 | if (dim) try stderr.setColor(.dim); | |
| 1414 | } | |
| 1415 | ||
| 1416 | try w.writeAll("\n"); | |
| 1417 | } else if (s.result_error_msgs.items.len > 0) { | |
| 1418 | try stderr.setColor(.red); | |
| 1419 | try w.writeAll(" failure\n"); | |
| 1420 | } else { | |
| 1421 | assert(s.result_stderr.len > 0); | |
| 1422 | try stderr.setColor(.red); | |
| 1423 | try w.writeAll(" w\n"); | |
| 1424 | } | |
| 1425 | } | |
| 1426 | ||
| 1427 | const PrintNode = struct { | |
| 1428 | parent: ?*PrintNode, | |
| 1429 | last: bool = false, | |
| 1430 | }; | |
| 1431 | ||
| 1432 | fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { | |
| 1433 | const parent = node.parent orelse return; | |
| 1434 | const writer = stderr.writer; | |
| 1435 | if (parent.parent == null) return; | |
| 1436 | try printPrefix(parent, stderr); | |
| 1437 | if (parent.last) { | |
| 1438 | try writer.writeAll(" "); | |
| 1439 | } else { | |
| 1440 | try writer.writeAll(switch (stderr.mode) { | |
| 1441 | .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ | |
| 1442 | else => "| ", | |
| 1443 | }); | |
| 1444 | } | |
| 1445 | } | |
| 1446 | ||
| 1447 | fn printChildNodePrefix(stderr: Io.Terminal) !void { | |
| 1448 | try stderr.writer.writeAll(switch (stderr.mode) { | |
| 1449 | .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ | |
| 1450 | else => "+- ", | |
| 1451 | }); | |
| 1452 | } | |
| 1453 | ||
| 1454 | /// Traverse the dependency graph depth-first and make it undirected by having | |
| 1455 | /// steps know their dependants (they only know dependencies at start). | |
| 1456 | /// Along the way, check that there is no dependency loop, and record the steps | |
| 1457 | /// in traversal order in `step_stack`. | |
| 1458 | /// Each step has its dependencies traversed in random order, this accomplishes | |
| 1459 | /// two things: | |
| 1460 | /// - `step_stack` will be in randomized-depth-first order, so the build runner | |
| 1461 | /// spawns initial steps in a random order | |
| 1462 | /// - each step's `dependants` list is also filled in a random order, so that | |
| 1463 | /// when it finishes executing in `makeStep`, it spawns next steps to run in | |
| 1464 | /// random order | |
| 1465 | fn constructGraphAndCheckForDependencyLoop( | |
| 1466 | maker: *Maker, | |
| 1467 | step_index: Configuration.Step.Index, | |
| 1468 | step_stack: *std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void), | |
| 1469 | rand: std.Random, | |
| 1470 | ) error{ DependencyLoopDetected, OutOfMemory }!void { | |
| 1471 | const c = &maker.scanned_config.configuration; | |
| 1472 | const gpa = maker.gpa; | |
| 1473 | const arena = maker.graph.arena; | |
| 1474 | const make_step = maker.stepByIndex(step_index); | |
| 1475 | switch (make_step.state) { | |
| 1476 | .precheck_started => { | |
| 1477 | log.err("dependency loop detected: {s}", .{step_index.ptr(c).name.slice(c)}); | |
| 1478 | return error.DependencyLoopDetected; | |
| 1479 | }, | |
| 1480 | .precheck_unstarted => { | |
| 1481 | make_step.state = .precheck_started; | |
| 1482 | ||
| 1483 | const step = step_index.ptr(c); | |
| 1484 | const dependencies = step.deps.slice(c); | |
| 1485 | try step_stack.ensureUnusedCapacity(gpa, dependencies.len); | |
| 1486 | ||
| 1487 | // We dupe to avoid shuffling the steps in the summary, it depends | |
| 1488 | // on dependencies' order. | |
| 1489 | const deps = try gpa.dupe(Configuration.Step.Index, dependencies); | |
| 1490 | defer gpa.free(deps); | |
| 1491 | ||
| 1492 | rand.shuffle(Configuration.Step.Index, deps); | |
| 1493 | ||
| 1494 | for (deps) |dep| { | |
| 1495 | const dep_step = maker.stepByIndex(dep); | |
| 1496 | try step_stack.put(gpa, dep, {}); | |
| 1497 | try dep_step.dependants.append(arena, step_index); | |
| 1498 | constructGraphAndCheckForDependencyLoop(maker, dep, step_stack, rand) catch |err| switch (err) { | |
| 1499 | error.DependencyLoopDetected => { | |
| 1500 | log.info("needed by: {s}", .{step_index.ptr(c).name.slice(c)}); | |
| 1501 | return err; | |
| 1502 | }, | |
| 1503 | else => return err, | |
| 1504 | }; | |
| 1505 | } | |
| 1506 | ||
| 1507 | make_step.state = .precheck_done; | |
| 1508 | make_step.pending_deps = @intCast(dependencies.len); | |
| 1509 | }, | |
| 1510 | .precheck_done => {}, | |
| 1511 | ||
| 1512 | // These don't happen until we actually run the step graph. | |
| 1513 | .dependency_failure => unreachable, | |
| 1514 | .success => unreachable, | |
| 1515 | .failure => unreachable, | |
| 1516 | .skipped => unreachable, | |
| 1517 | .skipped_oom => unreachable, | |
| 1518 | } | |
| 1519 | } | |
| 1520 | ||
| 1521 | /// When file watching, prepares the step for being re-evaluated. Returns | |
| 1522 | /// `true` if the step was newly invalidated, `false` if it was already | |
| 1523 | /// invalidated. | |
| 1524 | pub fn invalidateResult(maker: *Maker, step: *Step) bool { | |
| 1525 | if (step.state == .precheck_done) return false; | |
| 1526 | assert(step.pending_deps == 0); | |
| 1527 | step.state = .precheck_done; | |
| 1528 | step.reset(maker); | |
| 1529 | for (step.dependants.items) |dependant_index| { | |
| 1530 | const dependant = maker.stepByIndex(dependant_index); | |
| 1531 | _ = invalidateResult(maker, dependant); | |
| 1532 | dependant.pending_deps += 1; | |
| 1533 | } | |
| 1534 | return true; | |
| 1535 | } | |
| 1536 | ||
| 1537 | pub fn printErrorMessages( | |
| 1538 | maker: *Maker, | |
| 1539 | failing_step_index: Configuration.Step.Index, | |
| 1540 | options: std.zig.ErrorBundle.RenderOptions, | |
| 1541 | stderr: Io.Terminal, | |
| 1542 | error_style: ErrorStyle, | |
| 1543 | multiline_errors: MultilineErrors, | |
| 1544 | ) !void { | |
| 1545 | const c = &maker.scanned_config.configuration; | |
| 1546 | const gpa = maker.gpa; | |
| 1547 | const writer = stderr.writer; | |
| 1548 | if (error_style.verboseContext()) { | |
| 1549 | // Provide context for where these error messages are coming from by | |
| 1550 | // printing the corresponding Step subtree. | |
| 1551 | var step_stack: std.ArrayList(Configuration.Step.Index) = .empty; | |
| 1552 | defer step_stack.deinit(gpa); | |
| 1553 | try step_stack.append(gpa, failing_step_index); | |
| 1554 | while (true) { | |
| 1555 | const last_step = maker.stepByIndex(step_stack.items[step_stack.items.len - 1]); | |
| 1556 | if (last_step.dependants.items.len == 0) break; | |
| 1557 | try step_stack.append(gpa, last_step.dependants.items[0]); | |
| 1558 | } | |
| 1559 | ||
| 1560 | // Now, `step_stack` has the subtree that we want to print, in reverse order. | |
| 1561 | try stderr.setColor(.dim); | |
| 1562 | var indent: usize = 0; | |
| 1563 | while (step_stack.pop()) |step_index| : (indent += 1) { | |
| 1564 | if (indent > 0) { | |
| 1565 | try writer.splatByteAll(' ', (indent - 1) * 3); | |
| 1566 | try printChildNodePrefix(stderr); | |
| 1567 | } | |
| 1568 | ||
| 1569 | try writer.writeAll(step_index.ptr(c).name.slice(c)); | |
| 1570 | ||
| 1571 | if (step_index == failing_step_index) { | |
| 1572 | try printStepFailure(maker, step_index, stderr, true); | |
| 1573 | } else { | |
| 1574 | try writer.writeAll("\n"); | |
| 1575 | } | |
| 1576 | } | |
| 1577 | try stderr.setColor(.reset); | |
| 1578 | } else { | |
| 1579 | // Just print the failing step itself. | |
| 1580 | try stderr.setColor(.dim); | |
| 1581 | try writer.writeAll(failing_step_index.ptr(c).name.slice(c)); | |
| 1582 | try printStepFailure(maker, failing_step_index, stderr, true); | |
| 1583 | try stderr.setColor(.reset); | |
| 1584 | } | |
| 1585 | ||
| 1586 | const failing_step = maker.stepByIndex(failing_step_index); | |
| 1587 | ||
| 1588 | if (failing_step.result_stderr.len > 0) { | |
| 1589 | try writer.writeAll(failing_step.result_stderr); | |
| 1590 | if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) { | |
| 1591 | try writer.writeAll("\n"); | |
| 1592 | } | |
| 1593 | } | |
| 1594 | ||
| 1595 | try failing_step.result_error_bundle.renderToTerminal(options, stderr); | |
| 1596 | ||
| 1597 | for (failing_step.result_error_msgs.items) |msg| { | |
| 1598 | try stderr.setColor(.red); | |
| 1599 | try writer.writeAll("error:"); | |
| 1600 | try stderr.setColor(.reset); | |
| 1601 | if (std.mem.indexOfScalar(u8, msg, '\n') == null) { | |
| 1602 | try writer.print(" {s}\n", .{msg}); | |
| 1603 | } else switch (multiline_errors) { | |
| 1604 | .indent => { | |
| 1605 | var it = std.mem.splitScalar(u8, msg, '\n'); | |
| 1606 | try writer.print(" {s}\n", .{it.first()}); | |
| 1607 | while (it.next()) |line| { | |
| 1608 | try writer.print(" {s}\n", .{line}); | |
| 1609 | } | |
| 1610 | }, | |
| 1611 | .newline => try writer.print("\n{s}\n", .{msg}), | |
| 1612 | .none => try writer.print(" {s}\n", .{msg}), | |
| 1613 | } | |
| 1614 | } | |
| 1615 | ||
| 1616 | if (error_style.verboseContext()) { | |
| 1617 | if (failing_step.result_failed_command) |cmd_str| { | |
| 1618 | try stderr.setColor(.red); | |
| 1619 | try writer.writeAll("failed command: "); | |
| 1620 | try stderr.setColor(.reset); | |
| 1621 | try writer.writeAll(cmd_str); | |
| 1622 | try writer.writeByte('\n'); | |
| 1623 | } | |
| 1624 | } | |
| 1625 | ||
| 1626 | if (failing_step.result_oom) { | |
| 1627 | try stderr.setColor(.red); | |
| 1628 | try writer.writeAll("error information missing due to allocation failure"); | |
| 1629 | try stderr.setColor(.reset); | |
| 1630 | try writer.writeByte('\n'); | |
| 1631 | } | |
| 1632 | ||
| 1633 | try writer.writeByte('\n'); | |
| 1634 | } | |
| 1635 | ||
| 1636 | fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { | |
| 1637 | if (idx.* >= args.len) return null; | |
| 1638 | defer idx.* += 1; | |
| 1639 | return args[idx.*]; | |
| 1640 | } | |
| 1641 | ||
| 1642 | fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { | |
| 1643 | return nextArg(args, idx) orelse { | |
| 1644 | fatalWithHint("expected argument after {q}", .{args[idx.* - 1]}); | |
| 1645 | }; | |
| 1646 | } | |
| 1647 | ||
| 1648 | fn expectArgOrFatal(args: []const [:0]const u8, index_ptr: *usize, first: []const u8) []const u8 { | |
| 1649 | const next_arg = nextArg(args, index_ptr) orelse fatal("missing {q} argument", .{first}); | |
| 1650 | if (!mem.eql(u8, first, next_arg)) fatal("expected {q} instead of {q}", .{ first, next_arg }); | |
| 1651 | const arg = nextArg(args, index_ptr) orelse fatal("expected argument after {q}", .{first}); | |
| 1652 | return arg; | |
| 1653 | } | |
| 1654 | ||
| 1655 | fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 { | |
| 1656 | if (idx >= args.len) return null; | |
| 1657 | return args[idx..]; | |
| 1658 | } | |
| 1659 | ||
| 1660 | const Color = std.zig.Color; | |
| 1661 | const ErrorStyle = enum { | |
| 1662 | verbose, | |
| 1663 | minimal, | |
| 1664 | verbose_clear, | |
| 1665 | minimal_clear, | |
| 1666 | fn verboseContext(s: ErrorStyle) bool { | |
| 1667 | return switch (s) { | |
| 1668 | .verbose, .verbose_clear => true, | |
| 1669 | .minimal, .minimal_clear => false, | |
| 1670 | }; | |
| 1671 | } | |
| 1672 | fn clearOnUpdate(s: ErrorStyle) bool { | |
| 1673 | return switch (s) { | |
| 1674 | .verbose, .minimal => false, | |
| 1675 | .verbose_clear, .minimal_clear => true, | |
| 1676 | }; | |
| 1677 | } | |
| 1678 | }; | |
| 1679 | const MultilineErrors = enum { indent, newline, none }; | |
| 1680 | const Summary = enum { all, new, failures, line, none }; | |
| 1681 | ||
| 1682 | fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { | |
| 1683 | log.info("to access the help menu: zig build -h", .{}); | |
| 1684 | fatal(f, args); | |
| 1685 | } | |
| 1686 | ||
| 1687 | fn cleanTmpFiles(maker: *Maker, steps: []const Configuration.Step.Index) void { | |
| 1688 | const graph = maker.graph; | |
| 1689 | const io = graph.io; | |
| 1690 | const conf = &maker.scanned_config.configuration; | |
| 1691 | ||
| 1692 | for (steps) |step_index| { | |
| 1693 | const conf_step = step_index.ptr(conf); | |
| 1694 | const wf = conf_step.extended.cast(conf, Configuration.Step.WriteFile) orelse continue; | |
| 1695 | if (wf.flags.mode != .tmp) continue; | |
| 1696 | const step = maker.stepByIndex(step_index); | |
| 1697 | if (step.state != .success) continue; | |
| 1698 | const tmp_path = generatedPath(maker, wf.generated_directory).*; | |
| 1699 | tmp_path.root_dir.handle.deleteTree(io, tmp_path.subPathOrDot()) catch |err| | |
| 1700 | log.warn("failed to delete temporary path {f}: {t}", .{ tmp_path, err }); | |
| 1701 | } | |
| 1702 | } | |
| 1703 | ||
| 1704 | var stdio_buffer_allocation: [256]u8 = undefined; | |
| 1705 | var stdout_writer_allocation: Io.File.Writer = undefined; | |
| 1706 | ||
| 1707 | fn initStdoutWriter(io: Io) *Writer { | |
| 1708 | stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation); | |
| 1709 | return &stdout_writer_allocation.interface; | |
| 1710 | } | |
| 1711 | ||
| 1712 | /// `asking_step` is only used for debugging purposes; it's the step being run | |
| 1713 | /// that is asking for the path. | |
| 1714 | pub fn resolveLazyPath( | |
| 1715 | maker: *const Maker, | |
| 1716 | arena: Allocator, | |
| 1717 | lazy_path: Configuration.LazyPath, | |
| 1718 | asking_step_index: Configuration.Step.Index, | |
| 1719 | ) error{ OutOfMemory, MakeFailed }!Path { | |
| 1720 | const c = &maker.scanned_config.configuration; | |
| 1721 | return switch (lazy_path) { | |
| 1722 | .source_path => |sp| try packagePath(maker, arena, sp.owner, sp.sub_path.slice(c)), | |
| 1723 | .relative => |relative| relativePath(maker, arena, relative), | |
| 1724 | .generated => |gen| { | |
| 1725 | const base = generatedPath(maker, gen.index).*; | |
| 1726 | var file_path = base; | |
| 1727 | for (0..gen.flags.up) |_| { | |
| 1728 | file_path.sub_path = Dir.path.dirname(file_path.sub_path) orelse { | |
| 1729 | const s = stepByIndex(maker, asking_step_index); | |
| 1730 | return s.fail(maker, "invalid LazyPath traversal: up {d} times from {f}", .{ | |
| 1731 | gen.flags.up, base, | |
| 1732 | }); | |
| 1733 | }; | |
| 1734 | } | |
| 1735 | return file_path.join(arena, gen.sub_path.slice(c)); | |
| 1736 | }, | |
| 1737 | }; | |
| 1738 | } | |
| 1739 | ||
| 1740 | pub fn resolveLazyPathIndex( | |
| 1741 | maker: *const Maker, | |
| 1742 | arena: Allocator, | |
| 1743 | lazy_path_index: Configuration.LazyPath.Index, | |
| 1744 | asking_step_index: Configuration.Step.Index, | |
| 1745 | ) error{ OutOfMemory, MakeFailed }!Path { | |
| 1746 | const c = &maker.scanned_config.configuration; | |
| 1747 | return resolveLazyPath(maker, arena, lazy_path_index.get(c), asking_step_index); | |
| 1748 | } | |
| 1749 | ||
| 1750 | /// `resolveLazyPath` is preferred, but this can be necessary when passing Path | |
| 1751 | /// objects to child processes. | |
| 1752 | pub fn resolveLazyPathAbs( | |
| 1753 | maker: *const Maker, | |
| 1754 | arena: Allocator, | |
| 1755 | lazy_path: Configuration.LazyPath, | |
| 1756 | asking_step_index: Configuration.Step.Index, | |
| 1757 | ) error{ OutOfMemory, MakeFailed }![]const u8 { | |
| 1758 | const p = try resolveLazyPath(maker, arena, lazy_path, asking_step_index); | |
| 1759 | const root_dir_path = p.root_dir.path orelse return p.subPathOrDot(); | |
| 1760 | if (p.sub_path.len == 0) return root_dir_path; | |
| 1761 | return Dir.path.join(arena, &.{ root_dir_path, p.sub_path }); | |
| 1762 | } | |
| 1763 | ||
| 1764 | /// `resolveLazyPath` is preferred, but this can be necessary when passing Path | |
| 1765 | /// objects to child processes. | |
| 1766 | pub fn resolveLazyPathIndexAbs( | |
| 1767 | maker: *const Maker, | |
| 1768 | arena: Allocator, | |
| 1769 | lazy_path_index: Configuration.LazyPath.Index, | |
| 1770 | asking_step_index: Configuration.Step.Index, | |
| 1771 | ) error{ OutOfMemory, MakeFailed }![]const u8 { | |
| 1772 | const c = &maker.scanned_config.configuration; | |
| 1773 | return resolveLazyPathAbs(maker, arena, lazy_path_index.get(c), asking_step_index); | |
| 1774 | } | |
| 1775 | ||
| 1776 | pub fn generatedPath(maker: *const Maker, index: Configuration.GeneratedFileIndex) *Path { | |
| 1777 | return &maker.generated_files[@intFromEnum(index)]; | |
| 1778 | } | |
| 1779 | ||
| 1780 | pub fn packagePath( | |
| 1781 | maker: *const Maker, | |
| 1782 | arena: Allocator, | |
| 1783 | package_index: Configuration.Package.Index, | |
| 1784 | sub_path: []const u8, | |
| 1785 | ) Allocator.Error!Path { | |
| 1786 | const c = &maker.scanned_config.configuration; | |
| 1787 | const graph = maker.graph; | |
| 1788 | const package = package_index.get(c) orelse return .{ | |
| 1789 | .root_dir = graph.build_root_directory, | |
| 1790 | .sub_path = sub_path, | |
| 1791 | }; | |
| 1792 | // Currently, neither configurer nor Maker is aware of the standard zig | |
| 1793 | // package path, and the root path is stored as a bare string rather than | |
| 1794 | // relative to a known base directory. Without changing that, we must | |
| 1795 | // construct a cwd relative path here. | |
| 1796 | return .{ | |
| 1797 | .root_dir = .cwd(), | |
| 1798 | .sub_path = try Dir.path.join(arena, &.{ package.root_path.slice(c), sub_path }), | |
| 1799 | }; | |
| 1800 | } | |
| 1801 | ||
| 1802 | pub fn relativePath(maker: *const Maker, arena: Allocator, relative: Configuration.LazyPath.Relative) Allocator.Error!Path { | |
| 1803 | const graph = maker.graph; | |
| 1804 | const c = &maker.scanned_config.configuration; | |
| 1805 | const sub_path = relative.sub_path.slice(c); | |
| 1806 | return switch (relative.flags.base) { | |
| 1807 | .cwd => .{ | |
| 1808 | .root_dir = .cwd(), | |
| 1809 | .sub_path = sub_path, | |
| 1810 | }, | |
| 1811 | .local_cache => .{ | |
| 1812 | .root_dir = graph.local_cache_root, | |
| 1813 | .sub_path = sub_path, | |
| 1814 | }, | |
| 1815 | .global_cache => .{ | |
| 1816 | .root_dir = graph.global_cache_root, | |
| 1817 | .sub_path = sub_path, | |
| 1818 | }, | |
| 1819 | .build_root => .{ | |
| 1820 | .root_dir = graph.build_root_directory, | |
| 1821 | .sub_path = sub_path, | |
| 1822 | }, | |
| 1823 | .zig_exe => .{ | |
| 1824 | .root_dir = .cwd(), | |
| 1825 | .sub_path = if (sub_path.len == 0) | |
| 1826 | graph.zig_exe | |
| 1827 | else | |
| 1828 | try Io.Dir.path.join(arena, &.{ graph.zig_exe, sub_path }), | |
| 1829 | }, | |
| 1830 | .zig_lib => .{ | |
| 1831 | .root_dir = graph.zig_lib_directory, | |
| 1832 | .sub_path = sub_path, | |
| 1833 | }, | |
| 1834 | .install_prefix => maker.install_paths.prefix, | |
| 1835 | .install_lib => maker.install_paths.lib, | |
| 1836 | .install_bin => maker.install_paths.bin, | |
| 1837 | .install_include => maker.install_paths.include, | |
| 1838 | }; | |
| 1839 | } | |
| 1840 | ||
| 1841 | pub fn resolveInstallDir( | |
| 1842 | maker: *Maker, | |
| 1843 | arena: Allocator, | |
| 1844 | dest_dir: Configuration.InstallDestDir, | |
| 1845 | ) Allocator.Error!Path { | |
| 1846 | const c = &maker.scanned_config.configuration; | |
| 1847 | return switch (dest_dir.unpack().?) { | |
| 1848 | .prefix => maker.install_paths.prefix, | |
| 1849 | .lib => maker.install_paths.lib, | |
| 1850 | .bin => maker.install_paths.bin, | |
| 1851 | .header => maker.install_paths.include, | |
| 1852 | .sub_path => |s| try maker.install_paths.prefix.join(arena, s.slice(c)), | |
| 1853 | }; | |
| 1854 | } | |
| 1855 | ||
| 1856 | pub fn installLazyPathSub( | |
| 1857 | maker: *Maker, | |
| 1858 | arena: Allocator, | |
| 1859 | source: Configuration.LazyPath.Index, | |
| 1860 | dest_dir: Configuration.InstallDestDir, | |
| 1861 | sub_path: []const u8, | |
| 1862 | asking_step_index: Configuration.Step.Index, | |
| 1863 | ) !Dir.PrevStatus { | |
| 1864 | const src_path = try resolveLazyPathIndex(maker, arena, source, asking_step_index); | |
| 1865 | const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir); | |
| 1866 | const dest_path = try dest_dir_path.join(arena, sub_path); | |
| 1867 | return installPath(maker, arena, src_path, dest_path, asking_step_index); | |
| 1868 | } | |
| 1869 | ||
| 1870 | pub fn installLazyPath( | |
| 1871 | maker: *Maker, | |
| 1872 | arena: Allocator, | |
| 1873 | source: Configuration.LazyPath.Index, | |
| 1874 | dest_dir: Configuration.InstallDestDir, | |
| 1875 | asking_step_index: Configuration.Step.Index, | |
| 1876 | ) !Dir.PrevStatus { | |
| 1877 | const src_path = try resolveLazyPathIndex(maker, arena, source, asking_step_index); | |
| 1878 | const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir); | |
| 1879 | const dest_path = try dest_dir_path.join(arena, src_path.basename()); | |
| 1880 | return installPath(maker, arena, src_path, dest_path, asking_step_index); | |
| 1881 | } | |
| 1882 | ||
| 1883 | pub fn installGenerated( | |
| 1884 | maker: *Maker, | |
| 1885 | arena: Allocator, | |
| 1886 | source: Configuration.GeneratedFileIndex, | |
| 1887 | dest_dir: Configuration.InstallDestDir, | |
| 1888 | asking_step_index: Configuration.Step.Index, | |
| 1889 | ) !Dir.PrevStatus { | |
| 1890 | const src_path = generatedPath(maker, source).*; | |
| 1891 | const dest_dir_path = try resolveInstallDir(maker, arena, dest_dir); | |
| 1892 | const dest_path = try dest_dir_path.join(arena, src_path.basename()); | |
| 1893 | return installPath(maker, arena, src_path, dest_path, asking_step_index); | |
| 1894 | } | |
| 1895 | ||
| 1896 | pub fn truncatePath( | |
| 1897 | maker: *Maker, | |
| 1898 | arena: Allocator, | |
| 1899 | dest_path: Path, | |
| 1900 | asking_step_index: Configuration.Step.Index, | |
| 1901 | ) Step.ExtendedMakeError!void { | |
| 1902 | const graph = maker.graph; | |
| 1903 | const io = graph.io; | |
| 1904 | if (graph.verbose) try graph.handleVerbose(null, null, &.{ | |
| 1905 | "truncate", try dest_path.toString(arena), | |
| 1906 | }); | |
| 1907 | const err = e: { | |
| 1908 | var file = f: { | |
| 1909 | break :f dest_path.root_dir.handle.createFile(io, dest_path.sub_path, .{}) catch |err| switch (err) { | |
| 1910 | error.FileNotFound => { | |
| 1911 | const parent_path = dest_path.dirname() orelse break :e err; | |
| 1912 | parent_path.root_dir.handle.createDirPath(io, parent_path.sub_path) catch |in| switch (in) { | |
| 1913 | error.Canceled => |e| return e, | |
| 1914 | else => |e| { | |
| 1915 | const s = stepByIndex(maker, asking_step_index); | |
| 1916 | return s.fail(maker, "failed creating directory {f}: {t}", .{ parent_path, e }); | |
| 1917 | }, | |
| 1918 | }; | |
| 1919 | break :f dest_path.root_dir.handle.createFile(io, dest_path.sub_path, .{}) catch |in| break :e in; | |
| 1920 | }, | |
| 1921 | error.Canceled => |e| return e, | |
| 1922 | else => |e| break :e e, | |
| 1923 | }; | |
| 1924 | }; | |
| 1925 | file.close(io); | |
| 1926 | return; | |
| 1927 | }; | |
| 1928 | const s = stepByIndex(maker, asking_step_index); | |
| 1929 | return s.fail(maker, "failed truncating file {f}: {t}", .{ dest_path, err }); | |
| 1930 | } | |
| 1931 | ||
| 1932 | pub fn installPath( | |
| 1933 | maker: *Maker, | |
| 1934 | arena: Allocator, | |
| 1935 | src_path: Path, | |
| 1936 | dest_path: Path, | |
| 1937 | asking_step_index: Configuration.Step.Index, | |
| 1938 | ) Step.ExtendedMakeError!Dir.PrevStatus { | |
| 1939 | const graph = maker.graph; | |
| 1940 | const io = graph.io; | |
| 1941 | if (graph.verbose) try graph.handleVerbose(null, null, &.{ | |
| 1942 | "install", "-C", try src_path.toString(arena), try dest_path.toString(arena), | |
| 1943 | }); | |
| 1944 | return Dir.updateFile( | |
| 1945 | src_path.root_dir.handle, | |
| 1946 | io, | |
| 1947 | src_path.sub_path, | |
| 1948 | dest_path.root_dir.handle, | |
| 1949 | dest_path.sub_path, | |
| 1950 | .{}, | |
| 1951 | ) catch |err| { | |
| 1952 | const s = stepByIndex(maker, asking_step_index); | |
| 1953 | return s.fail(maker, "failed updating file from {f} to {f}: {t}", .{ src_path, dest_path, err }); | |
| 1954 | }; | |
| 1955 | } | |
| 1956 | ||
| 1957 | /// Wrapper around `Dir.createDirPathStatus` that handles verbose and error output. | |
| 1958 | pub fn installDir( | |
| 1959 | maker: *Maker, | |
| 1960 | arena: Allocator, | |
| 1961 | dest_path: Path, | |
| 1962 | asking_step_index: Configuration.Step.Index, | |
| 1963 | ) Step.ExtendedMakeError!Dir.CreatePathStatus { | |
| 1964 | const graph = maker.graph; | |
| 1965 | const io = graph.io; | |
| 1966 | if (graph.verbose) try graph.handleVerbose(null, null, &.{ | |
| 1967 | "install", "-d", try dest_path.toString(arena), | |
| 1968 | }); | |
| 1969 | return dest_path.root_dir.handle.createDirPathStatus(io, dest_path.sub_path, .default_dir) catch |err| { | |
| 1970 | const s = stepByIndex(maker, asking_step_index); | |
| 1971 | return s.fail(maker, "failed creating dir {f}: {t}", .{ dest_path, err }); | |
| 1972 | }; | |
| 1973 | } | |
| 1974 | ||
| 1975 | pub fn installSymLinks( | |
| 1976 | maker: *Maker, | |
| 1977 | arena: Allocator, | |
| 1978 | output_path: Path, | |
| 1979 | compile_step_index: Configuration.Step.Index, | |
| 1980 | asking_step_index: Configuration.Step.Index, | |
| 1981 | ) !void { | |
| 1982 | const c = &maker.scanned_config.configuration; | |
| 1983 | const conf_step = compile_step_index.ptr(c); | |
| 1984 | const conf_comp = conf_step.extended.get(c.extra).compile; | |
| 1985 | const root_module = conf_comp.root_module.get(c); | |
| 1986 | const target = root_module.resolved_target.get(c).?.result.get(c); | |
| 1987 | const os_tag = target.flags.os_tag.unwrap().?; | |
| 1988 | ||
| 1989 | assert(conf_comp.flags3.kind == .lib); | |
| 1990 | assert(conf_comp.flags2.linkage == .dynamic); | |
| 1991 | assert(os_tag != .windows); | |
| 1992 | ||
| 1993 | const version = std.SemanticVersion.parse(conf_comp.version.value.?.slice(c)) catch unreachable; | |
| 1994 | const name = conf_comp.root_name.slice(c); | |
| 1995 | ||
| 1996 | const filename_major_only, const filename_name_only = if (os_tag.isDarwin()) .{ | |
| 1997 | try std.fmt.allocPrint(arena, "lib{s}.{d}.dylib", .{ name, version.major }), | |
| 1998 | try std.fmt.allocPrint(arena, "lib{s}.dylib", .{name}), | |
| 1999 | } else .{ | |
| 2000 | try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ name, version.major }), | |
| 2001 | try std.fmt.allocPrint(arena, "lib{s}.so", .{name}), | |
| 2002 | }; | |
| 2003 | ||
| 2004 | return installSymLinksInner(maker, arena, output_path, asking_step_index, filename_major_only, filename_name_only); | |
| 2005 | } | |
| 2006 | ||
| 2007 | fn installSymLinksInner( | |
| 2008 | maker: *Maker, | |
| 2009 | arena: Allocator, | |
| 2010 | output_path: Path, | |
| 2011 | asking_step_index: Configuration.Step.Index, | |
| 2012 | filename_major_only: []const u8, | |
| 2013 | filename_name_only: []const u8, | |
| 2014 | ) !void { | |
| 2015 | const io = maker.graph.io; | |
| 2016 | const step = stepByIndex(maker, asking_step_index); | |
| 2017 | const out_basename = Io.Dir.path.basename(output_path.sub_path); | |
| 2018 | ||
| 2019 | const out_dir = output_path.dirname().?; | |
| 2020 | const major_only_path = try out_dir.join(arena, filename_major_only); | |
| 2021 | const name_only_path = try out_dir.join(arena, filename_name_only); | |
| 2022 | ||
| 2023 | // libfoo.so.1 to libfoo.so.1.2.3 | |
| 2024 | major_only_path.root_dir.handle.symLinkAtomic(io, out_basename, major_only_path.sub_path, .{}) catch |err| | |
| 2025 | return step.fail(maker, "failed symlinking {f} to {s}: {t}", .{ output_path, out_basename, err }); | |
| 2026 | ||
| 2027 | // libfoo.so to libfoo.so.1 | |
| 2028 | name_only_path.root_dir.handle.symLinkAtomic(io, filename_major_only, name_only_path.sub_path, .{}) catch |err| | |
| 2029 | return step.fail(maker, "failed symlinking {f} to {s}: {t}", .{ name_only_path, filename_major_only, err }); | |
| 2030 | } | |
| 2031 | ||
| 2032 | fn cleanExit(io: Io, scanned_config: *const ScannedConfig) void { | |
| 2033 | removePoisonedConfiguration(io, scanned_config); | |
| 2034 | return process.cleanExit(io); | |
| 2035 | } | |
| 2036 | ||
| 2037 | fn removePoisonedConfiguration(io: Io, scanned_config: *const ScannedConfig) void { | |
| 2038 | if (scanned_config.configuration.poisoned) { | |
| 2039 | // This configuration file was good for only 1 invocation of the maker | |
| 2040 | // process. Delete it to save space on disk. | |
| 2041 | Io.Dir.cwd().deleteFile(io, scanned_config.path) catch |err| | |
| 2042 | log.warn("failed deleting poisoned configuration file {s}: {t}", .{ scanned_config.path, err }); | |
| 2043 | } | |
| 2044 | } | |
| 2045 | ||
| 2046 | const is_debug_mode = builtin.mode == .Debug; | |
| 2047 | var debug_maker_leaks: bool = false; | |
| 2048 | inline fn debugMakerLeaks() bool { | |
| 2049 | if (!is_debug_mode) return false; | |
| 2050 | return debug_maker_leaks; | |
| 2051 | } |
lib/compiler/Maker/Fuzz.zig created+685| ... | ... | @@ -0,0 +1,685 @@ |
| 1 | const Fuzz = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | const Build = std.Build; | |
| 6 | const Cache = std.Build.Cache; | |
| 7 | const Coverage = std.debug.Coverage; | |
| 8 | const Configuration = std.Build.Configuration; | |
| 9 | const Io = std.Io; | |
| 10 | const abi = std.Build.abi.fuzz; | |
| 11 | const assert = std.debug.assert; | |
| 12 | const fatal = std.process.fatal; | |
| 13 | const log = std.log; | |
| 14 | ||
| 15 | const Maker = @import("../Maker.zig"); | |
| 16 | const WebServer = @import("WebServer.zig"); | |
| 17 | ||
| 18 | maker: *Maker, | |
| 19 | mode: Mode, | |
| 20 | ||
| 21 | /// Allocated into `gpa`. | |
| 22 | run_steps: []const Configuration.Step.Index, | |
| 23 | ||
| 24 | group: Io.Group, | |
| 25 | root_prog_node: std.Progress.Node, | |
| 26 | prog_node: std.Progress.Node, | |
| 27 | ||
| 28 | /// Protects `coverage_files`. | |
| 29 | coverage_mutex: Io.Mutex, | |
| 30 | coverage_files: std.AutoArrayHashMapUnmanaged(u64, CoverageMap), | |
| 31 | ||
| 32 | queue_mutex: Io.Mutex, | |
| 33 | queue_cond: Io.Condition, | |
| 34 | msg_queue: std.ArrayList(Msg), | |
| 35 | ||
| 36 | pub const Mode = union(enum) { | |
| 37 | forever: struct { ws: *WebServer }, | |
| 38 | limit: Limited, | |
| 39 | ||
| 40 | pub const Limited = struct { | |
| 41 | amount: u64, | |
| 42 | }; | |
| 43 | }; | |
| 44 | ||
| 45 | const Msg = union(enum) { | |
| 46 | coverage: struct { | |
| 47 | id: u64, | |
| 48 | cumulative: struct { | |
| 49 | runs: u64, | |
| 50 | unique: u64, | |
| 51 | coverage: u64, | |
| 52 | }, | |
| 53 | run: Configuration.Step.Index, | |
| 54 | }, | |
| 55 | entry_point: struct { | |
| 56 | coverage_id: u64, | |
| 57 | addr: u64, | |
| 58 | }, | |
| 59 | }; | |
| 60 | ||
| 61 | const CoverageMap = struct { | |
| 62 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 63 | coverage: Coverage, | |
| 64 | source_locations: []Coverage.SourceLocation, | |
| 65 | /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested. | |
| 66 | entry_points: std.ArrayList(u32), | |
| 67 | start_timestamp: i64, | |
| 68 | start_n_runs: u64, | |
| 69 | ||
| 70 | fn deinit(cm: *CoverageMap, gpa: Allocator) void { | |
| 71 | std.posix.munmap(cm.mapped_memory); | |
| 72 | cm.coverage.deinit(gpa); | |
| 73 | cm.* = undefined; | |
| 74 | } | |
| 75 | }; | |
| 76 | ||
| 77 | pub fn init( | |
| 78 | maker: *Maker, | |
| 79 | all_steps: []const Configuration.Step.Index, | |
| 80 | root_prog_node: std.Progress.Node, | |
| 81 | mode: Mode, | |
| 82 | ) error{ OutOfMemory, Canceled }!Fuzz { | |
| 83 | const graph = maker.graph; | |
| 84 | const gpa = graph.cache.gpa; | |
| 85 | const io = graph.io; | |
| 86 | const conf = &maker.scanned_config.configuration; | |
| 87 | ||
| 88 | const run_steps: []const Configuration.Step.Index = steps: { | |
| 89 | var steps: std.ArrayList(Configuration.Step.Index) = .empty; | |
| 90 | defer steps.deinit(gpa); | |
| 91 | const rebuild_node = root_prog_node.start("Rebuilding Unit Tests", 0); | |
| 92 | defer rebuild_node.end(); | |
| 93 | var rebuild_group: Io.Group = .init; | |
| 94 | defer rebuild_group.cancel(io); | |
| 95 | ||
| 96 | for (all_steps) |step_index| { | |
| 97 | const conf_run = step_index.ptr(conf).extended.cast(conf, Configuration.Step.Run) orelse continue; | |
| 98 | if (conf_run.producer.value == null) continue; | |
| 99 | const run = &maker.stepByIndex(step_index).extended.run; | |
| 100 | if (run.fuzz_tests.items.len == 0) continue; | |
| 101 | try steps.append(gpa, step_index); | |
| 102 | rebuild_group.async(io, rebuildTestsWorkerRun, .{ maker, step_index, rebuild_node }); | |
| 103 | } | |
| 104 | ||
| 105 | if (steps.items.len == 0) fatal("no fuzz tests found", .{}); | |
| 106 | rebuild_node.setEstimatedTotalItems(steps.items.len); | |
| 107 | const run_steps = try gpa.dupe(Configuration.Step.Index, steps.items); | |
| 108 | try rebuild_group.await(io); | |
| 109 | break :steps run_steps; | |
| 110 | }; | |
| 111 | errdefer gpa.free(run_steps); | |
| 112 | ||
| 113 | for (run_steps) |run_index| { | |
| 114 | const run = &maker.stepByIndex(run_index).extended.run; | |
| 115 | assert(run.fuzz_tests.items.len > 0); | |
| 116 | if (run.rebuilt_executable == null) | |
| 117 | fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{}); | |
| 118 | } | |
| 119 | ||
| 120 | return .{ | |
| 121 | .maker = maker, | |
| 122 | .mode = mode, | |
| 123 | .run_steps = run_steps, | |
| 124 | .group = .init, | |
| 125 | .root_prog_node = root_prog_node, | |
| 126 | .prog_node = .none, | |
| 127 | .coverage_files = .empty, | |
| 128 | .coverage_mutex = .init, | |
| 129 | .queue_mutex = .init, | |
| 130 | .queue_cond = .init, | |
| 131 | .msg_queue = .empty, | |
| 132 | }; | |
| 133 | } | |
| 134 | ||
| 135 | pub fn start(fuzz: *Fuzz) void { | |
| 136 | const maker = fuzz.maker; | |
| 137 | const graph = maker.graph; | |
| 138 | const io = graph.io; | |
| 139 | ||
| 140 | fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", 0); | |
| 141 | ||
| 142 | if (fuzz.mode == .forever) { | |
| 143 | // For polling messages and sending updates to subscribers. | |
| 144 | fuzz.group.concurrent(io, coverageRun, .{fuzz}) catch |err| | |
| 145 | fatal("unable to spawn coverage task: {t}", .{err}); | |
| 146 | } | |
| 147 | ||
| 148 | for (fuzz.run_steps) |run_index| { | |
| 149 | const run = &maker.stepByIndex(run_index).extended.run; | |
| 150 | assert(run.rebuilt_executable != null); | |
| 151 | fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run_index }); | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | pub fn deinit(fuzz: *Fuzz) void { | |
| 156 | const maker = fuzz.maker; | |
| 157 | const graph = maker.graph; | |
| 158 | const io = graph.io; | |
| 159 | const gpa = maker.gpa; | |
| 160 | ||
| 161 | fuzz.group.cancel(io); | |
| 162 | fuzz.prog_node.end(); | |
| 163 | gpa.free(fuzz.run_steps); | |
| 164 | } | |
| 165 | ||
| 166 | fn rebuildTestsWorkerRun( | |
| 167 | maker: *Maker, | |
| 168 | run_index: Configuration.Step.Index, | |
| 169 | parent_prog_node: std.Progress.Node, | |
| 170 | ) void { | |
| 171 | rebuildTestsWorkerRunFallible(maker, run_index, parent_prog_node) catch |err| { | |
| 172 | const conf = &maker.scanned_config.configuration; | |
| 173 | const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run).?; | |
| 174 | const comp_index = conf_run.producer.value.?; | |
| 175 | const step_name = comp_index.ptr(conf).name.slice(conf); | |
| 176 | log.err("step {s}: failed to rebuild in fuzz mode: {t}", .{ step_name, err }); | |
| 177 | }; | |
| 178 | } | |
| 179 | ||
| 180 | fn rebuildTestsWorkerRunFallible( | |
| 181 | maker: *Maker, | |
| 182 | run_index: Configuration.Step.Index, | |
| 183 | parent_prog_node: std.Progress.Node, | |
| 184 | ) !void { | |
| 185 | const graph = maker.graph; | |
| 186 | const io = graph.io; | |
| 187 | const gpa = maker.gpa; | |
| 188 | const conf = &maker.scanned_config.configuration; | |
| 189 | const run = &maker.stepByIndex(run_index).extended.run; | |
| 190 | const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run).?; | |
| 191 | const comp_index = conf_run.producer.value.?; | |
| 192 | const comp_step = maker.stepByIndex(comp_index); | |
| 193 | const comp = &comp_step.extended.compile; | |
| 194 | const conf_comp_step = comp_index.ptr(conf); | |
| 195 | const conf_comp = conf_comp_step.extended.cast(conf, Configuration.Step.Compile).?; | |
| 196 | const root_module = conf_comp.root_module.get(conf); | |
| 197 | const target = root_module.resolved_target.get(conf).?.result.get(conf); | |
| 198 | ||
| 199 | const prog_node = parent_prog_node.start(conf_comp_step.name.slice(conf), 0); | |
| 200 | defer prog_node.end(); | |
| 201 | ||
| 202 | const result = comp.rebuildInFuzzMode(maker, comp_index, prog_node); | |
| 203 | ||
| 204 | const show_compile_errors = comp_step.result_error_bundle.errorMessageCount() > 0; | |
| 205 | const show_error_msgs = comp_step.result_error_msgs.items.len > 0; | |
| 206 | const show_stderr = comp_step.result_stderr.len > 0; | |
| 207 | ||
| 208 | if (show_error_msgs or show_compile_errors or show_stderr) { | |
| 209 | var buf: [256]u8 = undefined; | |
| 210 | const stderr = try io.lockStderr(&buf, graph.stderr_mode); | |
| 211 | defer io.unlockStderr(); | |
| 212 | maker.printErrorMessages(comp_index, .{}, stderr.terminal(), .verbose, .indent) catch {}; | |
| 213 | } | |
| 214 | ||
| 215 | const rebuilt_bin_path = result catch |err| switch (err) { | |
| 216 | error.MakeFailed => return, | |
| 217 | else => |other| return other, | |
| 218 | }; | |
| 219 | const compile_filename = try std.zig.binNameAlloc(gpa, .{ | |
| 220 | .root_name = conf_comp.root_name.slice(conf), | |
| 221 | .cpu_arch = target.flags.cpu_arch.unwrap().?, | |
| 222 | .os_tag = target.flags.os_tag.unwrap().?, | |
| 223 | .ofmt = target.flags.object_format.unwrap().?, | |
| 224 | .abi = target.flags.abi.unwrap().?, | |
| 225 | .output_mode = switch (conf_comp.flags3.kind) { | |
| 226 | .lib => .Lib, | |
| 227 | .obj, .test_obj => .Obj, | |
| 228 | .exe, .@"test" => .Exe, | |
| 229 | }, | |
| 230 | .link_mode = conf_comp.flags2.linkage.unwrap(), | |
| 231 | .version = if (conf_comp.version.value) |v| | |
| 232 | std.SemanticVersion.parse(v.slice(conf)) catch unreachable | |
| 233 | else | |
| 234 | null, | |
| 235 | }); | |
| 236 | defer gpa.free(compile_filename); | |
| 237 | ||
| 238 | run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile_filename); | |
| 239 | } | |
| 240 | ||
| 241 | fn fuzzWorkerRun(fuzz: *Fuzz, run_index: Configuration.Step.Index) void { | |
| 242 | const maker = fuzz.maker; | |
| 243 | const graph = maker.graph; | |
| 244 | const io = graph.io; | |
| 245 | const conf = &maker.scanned_config.configuration; | |
| 246 | const run = &maker.stepByIndex(run_index).extended.run; | |
| 247 | ||
| 248 | run.rerunInFuzzMode(run_index, fuzz, fuzz.prog_node) catch |err| switch (err) { | |
| 249 | error.MakeFailed => { | |
| 250 | var buf: [256]u8 = undefined; | |
| 251 | const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) { | |
| 252 | error.Canceled => return, | |
| 253 | }; | |
| 254 | defer io.unlockStderr(); | |
| 255 | maker.printErrorMessages(run_index, .{}, stderr.terminal(), .verbose, .indent) catch {}; | |
| 256 | return; | |
| 257 | }, | |
| 258 | else => { | |
| 259 | const step_name = run_index.ptr(conf).name.slice(conf); | |
| 260 | log.err("step {s}: failed to rerun in fuzz mode: {t}", .{ step_name, err }); | |
| 261 | return; | |
| 262 | }, | |
| 263 | }; | |
| 264 | } | |
| 265 | ||
| 266 | pub fn serveSourcesTar(fuzz: *Fuzz, req: *std.http.Server.Request) !void { | |
| 267 | assert(fuzz.mode == .forever); | |
| 268 | const maker = fuzz.maker; | |
| 269 | const gpa = maker.gpa; | |
| 270 | const conf = &maker.scanned_config.configuration; | |
| 271 | ||
| 272 | var arena_state: std.heap.ArenaAllocator = .init(gpa); | |
| 273 | defer arena_state.deinit(); | |
| 274 | const arena = arena_state.allocator(); | |
| 275 | ||
| 276 | const DedupTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false); | |
| 277 | var dedup_table: DedupTable = .empty; | |
| 278 | defer dedup_table.deinit(gpa); | |
| 279 | ||
| 280 | for (fuzz.run_steps) |run_index| { | |
| 281 | const conf_run = run_index.ptr(conf).extended.cast(conf, Configuration.Step.Run) orelse continue; | |
| 282 | const comp_index = conf_run.producer.value.?; | |
| 283 | const comp_step = maker.stepByIndex(comp_index); | |
| 284 | const compile_inputs = comp_step.inputs.table; | |
| 285 | for (compile_inputs.keys(), compile_inputs.values()) |dir_path, *file_list| { | |
| 286 | try dedup_table.ensureUnusedCapacity(gpa, file_list.items.len); | |
| 287 | for (file_list.items) |sub_path| { | |
| 288 | if (!std.mem.endsWith(u8, sub_path, ".zig")) continue; | |
| 289 | const joined_path = try dir_path.join(arena, sub_path); | |
| 290 | dedup_table.putAssumeCapacity(joined_path, {}); | |
| 291 | } | |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | const deduped_paths = dedup_table.keys(); | |
| 296 | const SortContext = struct { | |
| 297 | pub fn lessThan(this: @This(), lhs: Build.Cache.Path, rhs: Build.Cache.Path) bool { | |
| 298 | _ = this; | |
| 299 | return switch (std.mem.order(u8, lhs.root_dir.path orelse ".", rhs.root_dir.path orelse ".")) { | |
| 300 | .lt => true, | |
| 301 | .gt => false, | |
| 302 | .eq => std.mem.lessThan(u8, lhs.sub_path, rhs.sub_path), | |
| 303 | }; | |
| 304 | } | |
| 305 | }; | |
| 306 | std.mem.sortUnstable(Build.Cache.Path, deduped_paths, SortContext{}, SortContext.lessThan); | |
| 307 | return fuzz.mode.forever.ws.serveTarFile(req, deduped_paths); | |
| 308 | } | |
| 309 | ||
| 310 | pub const Previous = struct { | |
| 311 | unique_runs: usize, | |
| 312 | entry_points: usize, | |
| 313 | sent_source_index: bool, | |
| 314 | pub const init: Previous = .{ | |
| 315 | .unique_runs = 0, | |
| 316 | .entry_points = 0, | |
| 317 | .sent_source_index = false, | |
| 318 | }; | |
| 319 | }; | |
| 320 | pub fn sendUpdate( | |
| 321 | fuzz: *Fuzz, | |
| 322 | socket: *std.http.Server.WebSocket, | |
| 323 | prev: *Previous, | |
| 324 | ) !void { | |
| 325 | const maker = fuzz.maker; | |
| 326 | const graph = maker.graph; | |
| 327 | const io = graph.io; | |
| 328 | ||
| 329 | try fuzz.coverage_mutex.lock(io); | |
| 330 | defer fuzz.coverage_mutex.unlock(io); | |
| 331 | ||
| 332 | const coverage_maps = fuzz.coverage_files.values(); | |
| 333 | if (coverage_maps.len == 0) return; | |
| 334 | // TODO: handle multiple fuzz steps in the WebSocket packets | |
| 335 | const coverage_map = &coverage_maps[0]; | |
| 336 | const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 337 | // TODO: this isn't sound! We need to do volatile reads of these bits rather than handing the | |
| 338 | // buffer off to the kernel, because we might race with the fuzzer process[es]. This brings the | |
| 339 | // whole mmap strategy into question. Incidentally, I wonder if post-writergate we could pass | |
| 340 | // this data straight to the socket with sendfile... | |
| 341 | const seen_pcs = cov_header.seenBits(); | |
| 342 | const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic); | |
| 343 | const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic); | |
| 344 | { | |
| 345 | if (!prev.sent_source_index) { | |
| 346 | prev.sent_source_index = true; | |
| 347 | // We need to send initial context. | |
| 348 | const header: abi.SourceIndexHeader = .{ | |
| 349 | .directories_len = @intCast(coverage_map.coverage.directories.entries.len), | |
| 350 | .files_len = @intCast(coverage_map.coverage.files.entries.len), | |
| 351 | .source_locations_len = @intCast(coverage_map.source_locations.len), | |
| 352 | .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len), | |
| 353 | .start_timestamp = coverage_map.start_timestamp, | |
| 354 | .start_n_runs = coverage_map.start_n_runs, | |
| 355 | }; | |
| 356 | var iovecs: [5][]const u8 = .{ | |
| 357 | @ptrCast(&header), | |
| 358 | @ptrCast(coverage_map.coverage.directories.keys()), | |
| 359 | @ptrCast(coverage_map.coverage.files.keys()), | |
| 360 | @ptrCast(coverage_map.source_locations), | |
| 361 | coverage_map.coverage.string_bytes.items, | |
| 362 | }; | |
| 363 | try socket.writeMessageVec(&iovecs, .binary); | |
| 364 | } | |
| 365 | ||
| 366 | const header: abi.CoverageUpdateHeader = .{ | |
| 367 | .n_runs = n_runs, | |
| 368 | .unique_runs = unique_runs, | |
| 369 | }; | |
| 370 | var iovecs: [2][]const u8 = .{ | |
| 371 | @ptrCast(&header), | |
| 372 | @ptrCast(seen_pcs), | |
| 373 | }; | |
| 374 | try socket.writeMessageVec(&iovecs, .binary); | |
| 375 | ||
| 376 | prev.unique_runs = unique_runs; | |
| 377 | } | |
| 378 | ||
| 379 | if (prev.entry_points != coverage_map.entry_points.items.len) { | |
| 380 | const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len)); | |
| 381 | var iovecs: [2][]const u8 = .{ | |
| 382 | @ptrCast(&header), | |
| 383 | @ptrCast(coverage_map.entry_points.items), | |
| 384 | }; | |
| 385 | try socket.writeMessageVec(&iovecs, .binary); | |
| 386 | ||
| 387 | prev.entry_points = coverage_map.entry_points.items.len; | |
| 388 | } | |
| 389 | } | |
| 390 | ||
| 391 | fn coverageRun(fuzz: *Fuzz) void { | |
| 392 | coverageRunCancelable(fuzz) catch |err| switch (err) { | |
| 393 | error.Canceled => return, | |
| 394 | }; | |
| 395 | } | |
| 396 | ||
| 397 | fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void { | |
| 398 | const maker = fuzz.maker; | |
| 399 | const graph = maker.graph; | |
| 400 | const io = graph.io; | |
| 401 | ||
| 402 | try fuzz.queue_mutex.lock(io); | |
| 403 | defer fuzz.queue_mutex.unlock(io); | |
| 404 | ||
| 405 | while (true) { | |
| 406 | try fuzz.queue_cond.wait(io, &fuzz.queue_mutex); | |
| 407 | for (fuzz.msg_queue.items) |msg| switch (msg) { | |
| 408 | .coverage => |coverage| prepareTables(fuzz, coverage.run, coverage.id) catch |err| switch (err) { | |
| 409 | error.AlreadyReported => continue, | |
| 410 | error.Canceled => return, | |
| 411 | else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}), | |
| 412 | }, | |
| 413 | .entry_point => |entry_point| addEntryPoint(fuzz, entry_point.coverage_id, entry_point.addr) catch |err| switch (err) { | |
| 414 | error.AlreadyReported => continue, | |
| 415 | error.Canceled => return, | |
| 416 | else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}), | |
| 417 | }, | |
| 418 | }; | |
| 419 | fuzz.msg_queue.clearRetainingCapacity(); | |
| 420 | } | |
| 421 | } | |
| 422 | fn prepareTables(fuzz: *Fuzz, run_index: Configuration.Step.Index, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void { | |
| 423 | assert(fuzz.mode == .forever); | |
| 424 | const ws = fuzz.mode.forever.ws; | |
| 425 | const maker = fuzz.maker; | |
| 426 | const graph = maker.graph; | |
| 427 | const io = graph.io; | |
| 428 | const gpa = maker.gpa; | |
| 429 | const conf = &maker.scanned_config.configuration; | |
| 430 | const cache_root = graph.local_cache_root; | |
| 431 | ||
| 432 | try fuzz.coverage_mutex.lock(io); | |
| 433 | defer fuzz.coverage_mutex.unlock(io); | |
| 434 | ||
| 435 | const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id); | |
| 436 | if (gop.found_existing) { | |
| 437 | // We are fuzzing the same executable with multiple threads. | |
| 438 | // Perhaps the same unit test; perhaps a different one. In any | |
| 439 | // case, since the coverage file is the same, we only have to | |
| 440 | // notice changes to that one file in order to learn coverage for | |
| 441 | // this particular executable. | |
| 442 | return; | |
| 443 | } | |
| 444 | errdefer _ = fuzz.coverage_files.pop(); | |
| 445 | ||
| 446 | gop.value_ptr.* = .{ | |
| 447 | .coverage = std.debug.Coverage.init, | |
| 448 | .mapped_memory = undefined, // populated below | |
| 449 | .source_locations = undefined, // populated below | |
| 450 | .entry_points = .empty, | |
| 451 | .start_timestamp = ws.now(), | |
| 452 | .start_n_runs = undefined, // populated below | |
| 453 | }; | |
| 454 | errdefer gop.value_ptr.coverage.deinit(gpa); | |
| 455 | ||
| 456 | const run_step = maker.stepByIndex(run_index); | |
| 457 | const conf_run_step = run_index.ptr(conf); | |
| 458 | const conf_run = conf_run_step.extended.cast(conf, Configuration.Step.Run).?; | |
| 459 | const comp_index = conf_run.producer.value.?; | |
| 460 | const conf_comp_step = comp_index.ptr(conf); | |
| 461 | const conf_comp = conf_comp_step.extended.cast(conf, Configuration.Step.Compile).?; | |
| 462 | const rebuilt_exe_path = run_step.extended.run.rebuilt_executable.?; | |
| 463 | const root_module = conf_comp.root_module.get(conf); | |
| 464 | const target = root_module.resolved_target.get(conf).?.result.get(conf); | |
| 465 | ||
| 466 | var debug_info = std.debug.Info.load( | |
| 467 | gpa, | |
| 468 | io, | |
| 469 | rebuilt_exe_path, | |
| 470 | &gop.value_ptr.coverage, | |
| 471 | target.flags.object_format.unwrap().?, | |
| 472 | target.flags.cpu_arch.unwrap().?, | |
| 473 | ) catch |err| { | |
| 474 | log.err("step {s}: failed to load debug information for {f}: {t}", .{ | |
| 475 | conf_run_step.name.slice(conf), rebuilt_exe_path, err, | |
| 476 | }); | |
| 477 | return error.AlreadyReported; | |
| 478 | }; | |
| 479 | defer debug_info.deinit(gpa); | |
| 480 | ||
| 481 | const coverage_file_path: Build.Cache.Path = .{ | |
| 482 | .root_dir = cache_root, | |
| 483 | .sub_path = "v/" ++ std.fmt.hex(coverage_id), | |
| 484 | }; | |
| 485 | var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| { | |
| 486 | log.err("step {s}: failed to load coverage file {f}: {t}", .{ | |
| 487 | conf_run_step.name.slice(conf), coverage_file_path, err, | |
| 488 | }); | |
| 489 | return error.AlreadyReported; | |
| 490 | }; | |
| 491 | defer coverage_file.close(io); | |
| 492 | ||
| 493 | const file_size = coverage_file.length(io) catch |err| { | |
| 494 | log.err("unable to check len of coverage file {f}: {t}", .{ coverage_file_path, err }); | |
| 495 | return error.AlreadyReported; | |
| 496 | }; | |
| 497 | ||
| 498 | const mapped_memory = std.posix.mmap( | |
| 499 | null, | |
| 500 | file_size, | |
| 501 | .{ .READ = true }, | |
| 502 | .{ .TYPE = .SHARED }, | |
| 503 | coverage_file.handle, | |
| 504 | 0, | |
| 505 | ) catch |err| { | |
| 506 | log.err("failed to map coverage file {f}: {t}", .{ coverage_file_path, err }); | |
| 507 | return error.AlreadyReported; | |
| 508 | }; | |
| 509 | gop.value_ptr.mapped_memory = mapped_memory; | |
| 510 | ||
| 511 | const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 512 | const pcs = header.pcAddrs(); | |
| 513 | const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len); | |
| 514 | errdefer gpa.free(source_locations); | |
| 515 | ||
| 516 | // Unfortunately the PCs array that LLVM gives us from the 8-bit PC | |
| 517 | // counters feature is not sorted. | |
| 518 | var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .empty; | |
| 519 | defer sorted_pcs.deinit(gpa); | |
| 520 | try sorted_pcs.resize(gpa, pcs.len); | |
| 521 | @memcpy(sorted_pcs.items(.pc), pcs); | |
| 522 | for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i); | |
| 523 | sorted_pcs.sortUnstable(struct { | |
| 524 | addrs: []const u64, | |
| 525 | ||
| 526 | pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool { | |
| 527 | return ctx.addrs[a_index] < ctx.addrs[b_index]; | |
| 528 | } | |
| 529 | }{ .addrs = sorted_pcs.items(.pc) }); | |
| 530 | ||
| 531 | debug_info.resolveAddresses(gpa, io, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| { | |
| 532 | log.err("failed to resolve addresses to source locations: {t}", .{err}); | |
| 533 | return error.AlreadyReported; | |
| 534 | }; | |
| 535 | ||
| 536 | for (sorted_pcs.items(.index), sorted_pcs.items(.sl)) |i, sl| source_locations[i] = sl; | |
| 537 | gop.value_ptr.source_locations = source_locations; | |
| 538 | gop.value_ptr.start_n_runs = header.n_runs; | |
| 539 | ||
| 540 | ws.notifyUpdate(); | |
| 541 | } | |
| 542 | ||
| 543 | fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReported, OutOfMemory, Canceled }!void { | |
| 544 | const maker = fuzz.maker; | |
| 545 | const graph = maker.graph; | |
| 546 | const io = graph.io; | |
| 547 | const gpa = maker.gpa; | |
| 548 | ||
| 549 | try fuzz.coverage_mutex.lock(io); | |
| 550 | defer fuzz.coverage_mutex.unlock(io); | |
| 551 | ||
| 552 | const coverage_map = fuzz.coverage_files.getPtr(coverage_id).?; | |
| 553 | const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 554 | const pcs = header.pcAddrs(); | |
| 555 | ||
| 556 | // Since this pcs list is unsorted, we must linear scan for the best index. | |
| 557 | const index = i: { | |
| 558 | var best: usize = 0; | |
| 559 | for (pcs[1..], 1..) |elem_addr, i| { | |
| 560 | if (elem_addr == addr) break :i i; | |
| 561 | if (elem_addr > addr) continue; | |
| 562 | if (elem_addr > pcs[best]) best = i; | |
| 563 | } | |
| 564 | break :i best; | |
| 565 | }; | |
| 566 | if (index >= pcs.len) { | |
| 567 | log.err("unable to find unit test entry address 0x{x} in source locations (range: 0x{x} to 0x{x})", .{ | |
| 568 | addr, pcs[0], pcs[pcs.len - 1], | |
| 569 | }); | |
| 570 | return error.AlreadyReported; | |
| 571 | } | |
| 572 | if (false) { | |
| 573 | const sl = coverage_map.source_locations[index]; | |
| 574 | const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename); | |
| 575 | if (pcs.len == 1) { | |
| 576 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{ | |
| 577 | addr, file_name, sl.line, sl.column, | |
| 578 | }); | |
| 579 | } else if (index == 0) { | |
| 580 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 before {x}", .{ | |
| 581 | addr, file_name, sl.line, sl.column, pcs[index + 1], | |
| 582 | }); | |
| 583 | } else if (index == pcs.len - 1) { | |
| 584 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} (final) after {x}", .{ | |
| 585 | addr, file_name, sl.line, sl.column, index, pcs[index - 1], | |
| 586 | }); | |
| 587 | } else { | |
| 588 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{ | |
| 589 | addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1], | |
| 590 | }); | |
| 591 | } | |
| 592 | } | |
| 593 | try coverage_map.entry_points.append(gpa, @intCast(index)); | |
| 594 | } | |
| 595 | ||
| 596 | pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void { | |
| 597 | assert(fuzz.mode == .limit); | |
| 598 | const maker = fuzz.maker; | |
| 599 | const graph = maker.graph; | |
| 600 | const io = graph.io; | |
| 601 | const cache_root = graph.local_cache_root; | |
| 602 | const conf = &maker.scanned_config.configuration; | |
| 603 | ||
| 604 | try fuzz.group.await(io); | |
| 605 | fuzz.group = .init; | |
| 606 | ||
| 607 | std.debug.print("======= FUZZING REPORT =======\n", .{}); | |
| 608 | for (fuzz.msg_queue.items) |msg| { | |
| 609 | if (msg != .coverage) continue; | |
| 610 | ||
| 611 | const cov = msg.coverage; | |
| 612 | const run_step_name = cov.run.ptr(conf).name.slice(conf); | |
| 613 | const run = &maker.stepByIndex(cov.run).extended.run; | |
| 614 | const coverage_file_path: std.Build.Cache.Path = .{ | |
| 615 | .root_dir = cache_root, | |
| 616 | .sub_path = "v/" ++ std.fmt.hex(cov.id), | |
| 617 | }; | |
| 618 | var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| { | |
| 619 | fatal("step {s}: failed to load coverage file {f}: {t}", .{ | |
| 620 | run_step_name, coverage_file_path, err, | |
| 621 | }); | |
| 622 | }; | |
| 623 | defer coverage_file.close(io); | |
| 624 | ||
| 625 | const fuzz_abi = std.Build.abi.fuzz; | |
| 626 | var rbuf: [0x1000]u8 = undefined; | |
| 627 | var r = coverage_file.reader(io, &rbuf); | |
| 628 | ||
| 629 | var header: fuzz_abi.SeenPcsHeader = undefined; | |
| 630 | r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| { | |
| 631 | fatal("step {s}: failed to read from coverage file {f}: {t}", .{ | |
| 632 | run_step_name, coverage_file_path, err, | |
| 633 | }); | |
| 634 | }; | |
| 635 | ||
| 636 | if (header.pcs_len == 0) { | |
| 637 | fatal("step {s}: corrupted coverage file {f}: pcs_len was zero", .{ | |
| 638 | run_step_name, coverage_file_path, | |
| 639 | }); | |
| 640 | } | |
| 641 | ||
| 642 | var seen_count: usize = 0; | |
| 643 | const chunk_count = fuzz_abi.SeenPcsHeader.seenElemsLen(header.pcs_len); | |
| 644 | for (0..chunk_count) |_| { | |
| 645 | const seen = r.interface.takeInt(usize, .little) catch |err| { | |
| 646 | fatal("step {s}: failed to read from coverage file {f}: {t}", .{ | |
| 647 | run_step_name, coverage_file_path, err, | |
| 648 | }); | |
| 649 | }; | |
| 650 | seen_count += @popCount(seen); | |
| 651 | } | |
| 652 | ||
| 653 | const seen_f: f64 = @floatFromInt(seen_count); | |
| 654 | const total_f: f64 = @floatFromInt(header.pcs_len); | |
| 655 | const ratio = seen_f / total_f; | |
| 656 | std.debug.print( | |
| 657 | \\Step: {s} | |
| 658 | \\Fuzz test: "{s}" ({x}) | |
| 659 | \\Runs: {} -> {} | |
| 660 | \\Unique runs: {} -> {} | |
| 661 | \\Coverage: {}/{} -> {}/{} ({:.02}%) | |
| 662 | \\ | |
| 663 | , .{ | |
| 664 | run_step_name, | |
| 665 | run.fuzz_tests.items[0], | |
| 666 | cov.id, | |
| 667 | cov.cumulative.runs, | |
| 668 | header.n_runs, | |
| 669 | cov.cumulative.unique, | |
| 670 | header.unique_runs, | |
| 671 | cov.cumulative.coverage, | |
| 672 | header.pcs_len, | |
| 673 | seen_count, | |
| 674 | header.pcs_len, | |
| 675 | ratio * 100, | |
| 676 | }); | |
| 677 | ||
| 678 | std.debug.print("------------------------------\n", .{}); | |
| 679 | } | |
| 680 | std.debug.print( | |
| 681 | \\Values are accumulated across multiple runs when preserving the cache. | |
| 682 | \\============================== | |
| 683 | \\ | |
| 684 | , .{}); | |
| 685 | } |
lib/compiler/Maker/Graph.zig created+85| ... | ... | @@ -0,0 +1,85 @@ |
| 1 | //! Shared maker state among all steps. | |
| 2 | const Graph = @This(); | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 6 | const Allocator = std.mem.Allocator; | |
| 7 | const Configuration = std.Build.Configuration; | |
| 8 | const Path = std.Build.Cache.Path; | |
| 9 | const Directory = std.Build.Cache.Directory; | |
| 10 | ||
| 11 | io: Io, | |
| 12 | /// Process lifetime. | |
| 13 | arena: Allocator, | |
| 14 | cache: std.Build.Cache, | |
| 15 | zig_exe: []const u8, | |
| 16 | environ_map: std.process.Environ.Map, | |
| 17 | global_cache_root: Directory, | |
| 18 | local_cache_root: Directory, | |
| 19 | zig_lib_directory: Directory, | |
| 20 | build_root_directory: Directory, | |
| 21 | ||
| 22 | debug_compiler_runtime_libs: ?std.builtin.OptimizeMode = null, | |
| 23 | incremental: ?bool = null, | |
| 24 | random_seed: u32 = 0, | |
| 25 | allow_so_scripts: ?bool = null, | |
| 26 | time_report: bool = false, | |
| 27 | /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also | |
| 28 | /// respects the '--color' flag. | |
| 29 | stderr_mode: ?Io.Terminal.Mode = null, | |
| 30 | reference_trace: ?u32 = null, | |
| 31 | debug_log_scopes: std.ArrayList([]const u8) = .empty, | |
| 32 | debug_compile_errors: bool = false, | |
| 33 | debug_incremental: bool = false, | |
| 34 | fuzzing: bool = false, | |
| 35 | verbose: bool = false, | |
| 36 | verbose_air: bool = false, | |
| 37 | verbose_cc: bool = false, | |
| 38 | verbose_link: bool = false, | |
| 39 | verbose_llvm_cpu_features: bool = false, | |
| 40 | verbose_llvm_ir: bool = false, | |
| 41 | libc_file: ?[]const u8 = null, | |
| 42 | /// What does this do? Nobody bothered to document it, and I think it's a | |
| 43 | /// smelly option. So unless somebody deletes these passive aggressive comments | |
| 44 | /// and replaces them with actual documentation, I'm going to delete this | |
| 45 | /// option from the build system in a future release. In other words, this is | |
| 46 | /// deprecated due to lack of test coverage, lack of documentation, and a hunch | |
| 47 | /// that it's a bad option that should be avoided. | |
| 48 | sysroot: ?[]const u8 = null, | |
| 49 | search_prefixes: std.ArrayList([]const u8) = .empty, | |
| 50 | build_id: ?std.zig.BuildId = null, | |
| 51 | error_limit: ?u32 = null, | |
| 52 | /// Steps should use `io` to limit the number of jobs, however in the case of | |
| 53 | /// a single step spawning a fixed number of processes this can be used. | |
| 54 | max_jobs: ?u32 = null, | |
| 55 | ||
| 56 | /// After following the steps in https://codeberg.org/ziglang/infra/src/branch/master/libc-update/glibc.md, | |
| 57 | /// this will be the directory $glibc-build-dir/install/glibcs | |
| 58 | /// Given the example of the aarch64 target, this is the directory | |
| 59 | /// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`. | |
| 60 | /// Also works for dynamic musl. | |
| 61 | libc_runtimes_dir: ?[]const u8 = null, | |
| 62 | enable_wine: bool = false, | |
| 63 | enable_qemu: bool = false, | |
| 64 | enable_wasmtime: bool = false, | |
| 65 | enable_darling: bool = false, | |
| 66 | enable_rosetta: bool = false, | |
| 67 | ||
| 68 | /// Intention of verbose is to print all sub-process command lines to stderr | |
| 69 | /// before spawning them. | |
| 70 | pub fn handleVerbose( | |
| 71 | graph: *const Graph, | |
| 72 | cwd: ?[]const u8, | |
| 73 | opt_env: ?*const std.process.Environ.Map, | |
| 74 | argv: []const []const u8, | |
| 75 | ) error{OutOfMemory}!void { | |
| 76 | if (!graph.verbose) return; | |
| 77 | const arena = graph.arena; | |
| 78 | const text = try std.zig.allocPrintCmd(arena, argv, .{ | |
| 79 | .cwd = cwd, | |
| 80 | .parent_env = &graph.environ_map, | |
| 81 | .child_env = opt_env, | |
| 82 | }); | |
| 83 | defer arena.free(text); | |
| 84 | std.log.scoped(.verbose).info("{s}", .{text}); | |
| 85 | } |
lib/compiler/Maker/PkgConfig.zig created+114| ... | ... | @@ -0,0 +1,114 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Io = std.Io; | |
| 3 | const mem = std.mem; | |
| 4 | const assert = std.debug.assert; | |
| 5 | ||
| 6 | const Maker = @import("../Maker.zig"); | |
| 7 | const Step = @import("Step.zig"); | |
| 8 | const Graph = @import("Graph.zig"); | |
| 9 | ||
| 10 | mutex: Io.Mutex = .init, | |
| 11 | pkgs: ?std.zig.PkgConfig = null, | |
| 12 | debug: bool = false, | |
| 13 | ||
| 14 | pub const RunError = error{ | |
| 15 | PackageNotFound, | |
| 16 | PkgConfigUnavailable, | |
| 17 | } || Step.ExtendedMakeError; | |
| 18 | ||
| 19 | pub const Result = std.zig.PkgConfig.Parsed; | |
| 20 | ||
| 21 | /// Run pkg-config for the given library name and parse the output, returning the arguments | |
| 22 | /// that should be passed to zig to link the given library. | |
| 23 | pub fn run( | |
| 24 | maker: *Maker, | |
| 25 | step: *Step, | |
| 26 | progress_node: std.Progress.Node, | |
| 27 | lib_name: []const u8, | |
| 28 | /// If true, reports failure error messages on step rather than returning | |
| 29 | /// error.PackageNotFound or error.PkgConfigUnavailable, | |
| 30 | force: bool, | |
| 31 | ) RunError!Result { | |
| 32 | const pc = &maker.pkg_config; | |
| 33 | const graph = maker.graph; | |
| 34 | const arena = graph.arena; // TODO don't leak into process arena | |
| 35 | ||
| 36 | const pkg_config_exe = getExe(graph); | |
| 37 | const pkgs = try getPkgs(maker, step, progress_node, force); | |
| 38 | const found_index = pkgs.find(lib_name) orelse { | |
| 39 | if (force) return step.fail(maker, "{s}: package not found: {s}", .{ pkg_config_exe, lib_name }); | |
| 40 | return error.PackageNotFound; | |
| 41 | }; | |
| 42 | const pkg = pkgs.all[found_index]; | |
| 43 | ||
| 44 | const stdout = try captureChildProcess(maker, step, .{ | |
| 45 | .argv = &.{ pkg_config_exe, pkg.name, "--cflags", "--libs" }, | |
| 46 | .progress_node = progress_node, | |
| 47 | .allow_failure = !force, | |
| 48 | }); | |
| 49 | ||
| 50 | const parsed = std.zig.PkgConfig.parse(arena, stdout) catch |err| switch (err) { | |
| 51 | error.InvalidPkgConfigOutput => { | |
| 52 | if (force) return step.fail(maker, "{s} package {s} invalid output: {s}", .{ | |
| 53 | pkg_config_exe, lib_name, stdout, | |
| 54 | }); | |
| 55 | return error.PkgConfigUnavailable; | |
| 56 | }, | |
| 57 | else => |e| return e, | |
| 58 | }; | |
| 59 | if (force or pc.debug) { | |
| 60 | for (parsed.unknown_flags) |unknown_flag| { | |
| 61 | return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, unknown_flag }); | |
| 62 | } | |
| 63 | } | |
| 64 | ||
| 65 | return parsed; | |
| 66 | } | |
| 67 | ||
| 68 | fn getExe(graph: *const Graph) []const u8 { | |
| 69 | return std.zig.PkgConfig.exe(&graph.environ_map); | |
| 70 | } | |
| 71 | ||
| 72 | fn getPkgs(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: bool) RunError!std.zig.PkgConfig { | |
| 73 | const graph = maker.graph; | |
| 74 | const arena = graph.arena; // TODO don't leak into process arena | |
| 75 | const io = graph.io; | |
| 76 | const pc = &maker.pkg_config; | |
| 77 | ||
| 78 | try pc.mutex.lock(io); | |
| 79 | defer pc.mutex.unlock(io); | |
| 80 | ||
| 81 | if (pc.pkgs) |pkgs| return pkgs; | |
| 82 | ||
| 83 | const pkg_config_exe = getExe(graph); | |
| 84 | const stdout = try captureChildProcess(maker, step, .{ | |
| 85 | .argv = &.{ pkg_config_exe, "--list-all" }, | |
| 86 | .progress_node = progress_node, | |
| 87 | .allow_failure = !force, | |
| 88 | }); | |
| 89 | ||
| 90 | var diagnostic: std.zig.PkgConfig.Diagnostic = undefined; | |
| 91 | const result = std.zig.PkgConfig.init(arena, stdout, &diagnostic) catch |err| switch (err) { | |
| 92 | error.InvalidPkgConfigOutput => { | |
| 93 | if (force) return step.fail(maker, "{s}: invalid line({d}): {s}", .{ | |
| 94 | pkg_config_exe, diagnostic.invalid_line_index + 1, diagnostic.invalid_line, | |
| 95 | }); | |
| 96 | return error.PkgConfigUnavailable; | |
| 97 | }, | |
| 98 | else => |e| return e, | |
| 99 | }; | |
| 100 | ||
| 101 | pc.pkgs = result; | |
| 102 | return result; | |
| 103 | } | |
| 104 | ||
| 105 | fn captureChildProcess(maker: *Maker, step: *Step, options: Step.CaptureChildProcessOptions) ![]const u8 { | |
| 106 | const captured = step.captureChildProcess(maker, options) catch |err| switch (err) { | |
| 107 | error.FileNotFound => return error.PkgConfigUnavailable, | |
| 108 | else => |e| return e, | |
| 109 | }; | |
| 110 | assert(step.result_failed_command != null); | |
| 111 | if (captured.term.success()) return captured.stdout; | |
| 112 | if (!options.allow_failure) return step.fail(maker, "{s} {f}", .{ options.argv[0], captured.term }); | |
| 113 | return error.PkgConfigUnavailable; | |
| 114 | } |
lib/compiler/Maker/ScannedConfig.zig created+370| ... | ... | @@ -0,0 +1,370 @@ |
| 1 | const ScannedConfig = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Configuration = std.Build.Configuration; | |
| 5 | const Writer = std.Io.Writer; | |
| 6 | const Serializer = std.zon.Serializer; | |
| 7 | ||
| 8 | const Graph = @import("Graph.zig"); | |
| 9 | ||
| 10 | configuration: Configuration, | |
| 11 | top_level_steps: std.StringArrayHashMapUnmanaged(Configuration.Step.Index), | |
| 12 | path: []const u8, | |
| 13 | ||
| 14 | pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void { | |
| 15 | std.log.err("TODO also print paths", .{}); | |
| 16 | std.log.err("TODO also print unlazy deps", .{}); | |
| 17 | std.log.err("TODO also print system integrations", .{}); | |
| 18 | std.log.err("TODO also print available options", .{}); | |
| 19 | const c = &sc.configuration; | |
| 20 | var serializer: Serializer = .{ .writer = w }; | |
| 21 | var s = try serializer.beginStruct(.{}); | |
| 22 | ||
| 23 | { | |
| 24 | var tf = try s.beginTupleField("search_prefixes", .{}); | |
| 25 | for (c.search_prefixes) |string| try tf.field(string.slice(c), .{}); | |
| 26 | try tf.end(); | |
| 27 | } | |
| 28 | ||
| 29 | try s.field("default_step", @intFromEnum(c.default_step), .{}); | |
| 30 | { | |
| 31 | var sf = try s.beginStructField("top_level_steps", .{}); | |
| 32 | for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step| { | |
| 33 | try sf.field(name, @intFromEnum(step), .{}); | |
| 34 | } | |
| 35 | try sf.end(); | |
| 36 | } | |
| 37 | ||
| 38 | { | |
| 39 | var tf = try s.beginTupleField("steps", .{}); | |
| 40 | for (c.steps) |step| { | |
| 41 | var step_field = try tf.beginStructField(.{}); | |
| 42 | try printStruct(sc, &step_field, Configuration.Step, step); | |
| 43 | try step_field.end(); | |
| 44 | } | |
| 45 | try tf.end(); | |
| 46 | } | |
| 47 | ||
| 48 | try s.end(); | |
| 49 | } | |
| 50 | ||
| 51 | fn printStruct(sc: *const ScannedConfig, s: *Serializer.Struct, comptime S: type, v: S) !void { | |
| 52 | inline for (@typeInfo(S).@"struct".fields) |field| { | |
| 53 | try s.fieldPrefix(field.name); | |
| 54 | try printValue(sc, s.container.serializer, field.type, @field(v, field.name)); | |
| 55 | } | |
| 56 | } | |
| 57 | ||
| 58 | fn printValue(sc: *const ScannedConfig, s: *Serializer, comptime Field: type, field_value: Field) !void { | |
| 59 | const c = &sc.configuration; | |
| 60 | switch (Field) { | |
| 61 | Configuration.String => { | |
| 62 | try s.value(field_value.slice(c), .{}); | |
| 63 | }, | |
| 64 | Configuration.Deps.Index => { | |
| 65 | try printValue(sc, s, []const Configuration.Step.Index, field_value.get(c).steps.slice); | |
| 66 | }, | |
| 67 | Configuration.MaxRss => { | |
| 68 | try s.value(field_value.toBytes(), .{}); | |
| 69 | }, | |
| 70 | Configuration.Step.Run.Arg.Index => { | |
| 71 | var sub_struct = try s.beginStruct(.{}); | |
| 72 | try printStruct(sc, &sub_struct, Configuration.Step.Run.Arg, field_value.get(c)); | |
| 73 | try sub_struct.end(); | |
| 74 | }, | |
| 75 | Configuration.Step.ObjCopy.UpdateSection.Flags => { | |
| 76 | var sub_struct = try s.beginStruct(.{}); | |
| 77 | try printStruct(sc, &sub_struct, Field, field_value); | |
| 78 | try sub_struct.end(); | |
| 79 | }, | |
| 80 | Configuration.LazyPath.Index => { | |
| 81 | switch (field_value.get(c)) { | |
| 82 | inline else => |u| { | |
| 83 | var sub_struct = try s.beginStruct(.{}); | |
| 84 | try printStruct(sc, &sub_struct, @TypeOf(u), u); | |
| 85 | try sub_struct.end(); | |
| 86 | }, | |
| 87 | } | |
| 88 | }, | |
| 89 | else => switch (@typeInfo(Field)) { | |
| 90 | .int => try s.int(field_value), | |
| 91 | .pointer => |info| switch (info.size) { | |
| 92 | .slice => { | |
| 93 | var slice_field = try s.beginTuple(.{}); | |
| 94 | for (field_value) |elem| { | |
| 95 | try slice_field.fieldPrefix(); | |
| 96 | try printValue(sc, s, info.child, elem); | |
| 97 | } | |
| 98 | try slice_field.end(); | |
| 99 | }, | |
| 100 | else => comptime unreachable, | |
| 101 | }, | |
| 102 | .@"enum" => { | |
| 103 | if (@hasDecl(Field, "storage")) switch (Field.storage) { | |
| 104 | .extended => { | |
| 105 | var sub_struct = try s.beginStruct(.{}); | |
| 106 | switch (field_value.get(c.extra)) { | |
| 107 | inline else => |u| { | |
| 108 | try printStruct(sc, &sub_struct, @TypeOf(u), u); | |
| 109 | }, | |
| 110 | } | |
| 111 | try sub_struct.end(); | |
| 112 | }, | |
| 113 | .flag_optional => comptime unreachable, | |
| 114 | .flag_length_prefixed_list => comptime unreachable, | |
| 115 | .enum_optional => comptime unreachable, | |
| 116 | .union_list => comptime unreachable, | |
| 117 | .length_prefixed_list => comptime unreachable, | |
| 118 | .flag_list => comptime unreachable, | |
| 119 | .flag_union => comptime unreachable, | |
| 120 | .multi_list => comptime unreachable, | |
| 121 | } else if (std.enums.tagName(Field, field_value)) |name| { | |
| 122 | try s.ident(name); | |
| 123 | } else { | |
| 124 | try s.int(@intFromEnum(field_value)); | |
| 125 | } | |
| 126 | }, | |
| 127 | .@"struct" => |info| switch (info.layout) { | |
| 128 | .@"packed" => { | |
| 129 | try s.value(field_value, .{}); | |
| 130 | }, | |
| 131 | .@"extern" => { | |
| 132 | var sub_struct = try s.beginStruct(.{}); | |
| 133 | try printStruct(sc, &sub_struct, Field, field_value); | |
| 134 | try sub_struct.end(); | |
| 135 | }, | |
| 136 | .auto => switch (Field.storage) { | |
| 137 | .flag_optional, .enum_optional => { | |
| 138 | if (field_value.value) |some| { | |
| 139 | try printValue(sc, s, Field.Value, some); | |
| 140 | } else { | |
| 141 | try s.value(null, .{}); | |
| 142 | } | |
| 143 | }, | |
| 144 | .length_prefixed_list, .flag_length_prefixed_list, .flag_list => { | |
| 145 | try printValue(sc, s, @TypeOf(field_value.slice), field_value.slice); | |
| 146 | }, | |
| 147 | .extended => @compileError("TODO"), | |
| 148 | .union_list => { | |
| 149 | var slice_field = try s.beginTuple(.{}); | |
| 150 | for (field_value.slice(c.extra), 0..) |elem, i| switch (field_value.tag(c.extra, i)) { | |
| 151 | inline else => |tag| { | |
| 152 | var sub_struct = try s.beginStruct(.{}); | |
| 153 | try sub_struct.fieldPrefix(@tagName(tag)); | |
| 154 | try printValue(sc, s, @FieldType(Field.Union, @tagName(tag)), @enumFromInt(elem)); | |
| 155 | try sub_struct.end(); | |
| 156 | }, | |
| 157 | }; | |
| 158 | try slice_field.end(); | |
| 159 | }, | |
| 160 | .flag_union => try printValue(sc, s, Field.Union, field_value.u), | |
| 161 | .multi_list => @compileError("TODO"), | |
| 162 | }, | |
| 163 | }, | |
| 164 | .@"union" => { | |
| 165 | try printTaggedUnion(sc, s, field_value); | |
| 166 | }, | |
| 167 | else => @compileError("not implemented: " ++ @typeName(Field)), | |
| 168 | }, | |
| 169 | } | |
| 170 | } | |
| 171 | ||
| 172 | fn printTaggedUnion(sc: *const ScannedConfig, s: *Serializer, value: anytype) !void { | |
| 173 | switch (value) { | |
| 174 | inline else => |u, tag| { | |
| 175 | if (@TypeOf(u) == void) { | |
| 176 | try s.ident(@tagName(tag)); | |
| 177 | } else { | |
| 178 | var sub_struct = try s.beginStruct(.{}); | |
| 179 | try sub_struct.fieldPrefix(@tagName(tag)); | |
| 180 | try printValue(sc, s, @TypeOf(u), u); | |
| 181 | try sub_struct.end(); | |
| 182 | } | |
| 183 | }, | |
| 184 | } | |
| 185 | } | |
| 186 | ||
| 187 | pub fn printSteps(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { | |
| 188 | const arena = graph.arena; | |
| 189 | const c = &sc.configuration; | |
| 190 | for (sc.top_level_steps.keys(), sc.top_level_steps.values()) |name, step_index| { | |
| 191 | const step = step_index.ptr(c); | |
| 192 | const decorated_name = if (step_index == c.default_step) | |
| 193 | try std.fmt.allocPrint(arena, "{s} (default)", .{name}) | |
| 194 | else | |
| 195 | name; | |
| 196 | const top_level = step.extended.get(c.extra).top_level; | |
| 197 | const description = top_level.description.slice(c); | |
| 198 | try w.print(" {s:<28} {s}\n", .{ decorated_name, description }); | |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | pub fn printUsage(sc: *const ScannedConfig, graph: *Graph, w: *Writer) !void { | |
| 203 | const arena = graph.arena; | |
| 204 | ||
| 205 | try w.print( | |
| 206 | \\Usage: {s} build [steps] [options] | |
| 207 | \\ | |
| 208 | \\Steps: | |
| 209 | \\ | |
| 210 | , .{graph.zig_exe}); | |
| 211 | try printSteps(sc, graph, w); | |
| 212 | try w.writeAll( | |
| 213 | \\ | |
| 214 | \\Project-Specific Options: | |
| 215 | \\ | |
| 216 | ); | |
| 217 | ||
| 218 | const available_options = sc.configuration.available_options; | |
| 219 | if (available_options.len == 0) { | |
| 220 | try w.print(" (none)\n", .{}); | |
| 221 | } else { | |
| 222 | for (available_options) |option| { | |
| 223 | const name = option.name.slice(&sc.configuration); | |
| 224 | const description = option.description.slice(&sc.configuration); | |
| 225 | const help = try std.fmt.allocPrint(arena, " -D{s}=[{t}]", .{ name, option.type }); | |
| 226 | try w.print("{s:<30} {s}\n", .{ help, description }); | |
| 227 | if (option.enum_options.slice(&sc.configuration)) |enum_options| { | |
| 228 | const padding: [33]u8 = @splat(' '); | |
| 229 | try w.writeAll(padding ++ "Supported Values:\n"); | |
| 230 | for (enum_options) |enum_option_index| { | |
| 231 | const enum_option = enum_option_index.slice(&sc.configuration); | |
| 232 | try w.print(padding ++ " {s}\n", .{enum_option}); | |
| 233 | } | |
| 234 | } | |
| 235 | } | |
| 236 | } | |
| 237 | ||
| 238 | try w.writeAll( | |
| 239 | \\ | |
| 240 | \\System Integration Options: | |
| 241 | \\ --search-prefix [path] Add a path to look for binaries, libraries, headers | |
| 242 | \\ --sysroot [path] Set the system root directory (usually /) | |
| 243 | \\ --libc [file] Provide a file which specifies libc paths | |
| 244 | \\ | |
| 245 | \\ --system [pkgdir] Disable package fetching; enable all integrations | |
| 246 | \\ -fsys=[name] Enable a system integration | |
| 247 | \\ -fno-sys=[name] Disable a system integration | |
| 248 | \\ | |
| 249 | \\ -fdarling, -fno-darling Integration with system-installed Darling to | |
| 250 | \\ execute macOS programs on Linux hosts | |
| 251 | \\ (default: no) | |
| 252 | \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute | |
| 253 | \\ foreign-architecture programs on Linux hosts | |
| 254 | \\ (default: no) | |
| 255 | \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc | |
| 256 | \\ (e.g. glibc or musl) built for multiple foreign | |
| 257 | \\ architectures, allowing execution of non-native | |
| 258 | \\ programs that link with libc. | |
| 259 | \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on | |
| 260 | \\ ARM64 macOS hosts. (default: no) | |
| 261 | \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to | |
| 262 | \\ execute WASI binaries. (default: no) | |
| 263 | \\ -fwine, -fno-wine Integration with system-installed Wine to execute | |
| 264 | \\ Windows programs on Linux hosts. (default: no) | |
| 265 | \\ | |
| 266 | \\ Available System Integrations: Enabled: | |
| 267 | \\ | |
| 268 | ); | |
| 269 | if (sc.configuration.system_integrations.len == 0) { | |
| 270 | try w.writeAll(" (none) -\n"); | |
| 271 | } else { | |
| 272 | for (sc.configuration.system_integrations) |system_integration| { | |
| 273 | const name = system_integration.name.slice(&sc.configuration); | |
| 274 | const status = switch (system_integration.status) { | |
| 275 | .disabled => "no", | |
| 276 | .enabled => "yes", | |
| 277 | }; | |
| 278 | try w.print(" {s:<43} {s}\n", .{ name, status }); | |
| 279 | } | |
| 280 | } | |
| 281 | ||
| 282 | try w.writeAll( | |
| 283 | \\ | |
| 284 | \\General Options: | |
| 285 | \\ -h, --help Print this help to stdout and exit | |
| 286 | \\ -l, --list-steps Print available steps to stdout and exit | |
| 287 | \\ | |
| 288 | \\ -p, --prefix [path] Where to install files (default: zig-out) | |
| 289 | \\ --prefix-lib-dir [path] Where to install libraries | |
| 290 | \\ --prefix-exe-dir [path] Where to install executables | |
| 291 | \\ --prefix-include-dir [path] Where to install C header files | |
| 292 | \\ --release[=mode] Request release mode, optionally specifying a | |
| 293 | \\ preferred optimization mode: fast, safe, small | |
| 294 | \\ | |
| 295 | \\ --verbose Print commands before executing them | |
| 296 | \\ --color [auto|off|on] Enable or disable colored error messages | |
| 297 | \\ --error-style [style] Control how build errors are printed | |
| 298 | \\ verbose (Default) Report errors with full context | |
| 299 | \\ minimal Report errors after summary, excluding context like command lines | |
| 300 | \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update | |
| 301 | \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update | |
| 302 | \\ --multiline-errors [style] Control how multi-line error messages are printed | |
| 303 | \\ indent (Default) Indent non-initial lines to align with initial line | |
| 304 | \\ newline Include a leading newline so that the error message is on its own lines | |
| 305 | \\ none Print as usual so the first line is misaligned | |
| 306 | \\ --summary [mode] Control the printing of the build summary | |
| 307 | \\ all Print the build summary in its entirety | |
| 308 | \\ new Omit cached steps | |
| 309 | \\ failures (Default if short-lived) Only print failed steps | |
| 310 | \\ line (Default if long-lived) Only print the single-line summary | |
| 311 | \\ none Do not print the build summary | |
| 312 | \\ -j<N> Limit concurrent jobs (default is to use all CPU cores) | |
| 313 | \\ --maxrss <bytes> Limit memory usage (default is to use available memory) | |
| 314 | \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss | |
| 315 | \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded. | |
| 316 | \\ The timeout must include a unit: ns, us, ms, s, m, h | |
| 317 | \\ --watch Continuously rebuild when source files are modified | |
| 318 | \\ --debounce <ms> Delay before rebuilding after changed file detected | |
| 319 | \\ --webui[=ip] Enable the web interface on the given IP address | |
| 320 | \\ --fuzz[=limit] Continuously search for unit test failures with an optional | |
| 321 | \\ limit to the max number of iterations. The argument supports | |
| 322 | \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies | |
| 323 | \\ '--webui' when no limit is specified. | |
| 324 | \\ --time-report Force full rebuild and provide detailed information on | |
| 325 | \\ compilation time of Zig source code (implies '--webui') | |
| 326 | \\ -fincremental Enable incremental compilation | |
| 327 | \\ -fno-incremental Disable incremental compilation | |
| 328 | \\ | |
| 329 | \\Package Management Options: | |
| 330 | \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit | |
| 331 | \\ needed (Default) Lazy dependencies are fetched as needed | |
| 332 | \\ all Lazy dependencies are always fetched | |
| 333 | \\ --fork=[path], --fork [path] Override one or more projects from dependency tree | |
| 334 | \\ | |
| 335 | \\Advanced Options: | |
| 336 | \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error | |
| 337 | \\ -fno-reference-trace Disable reference trace | |
| 338 | \\ -fallow-so-scripts Allows .so files to be GNU ld scripts | |
| 339 | \\ -fno-allow-so-scripts (default) .so files must be ELF files | |
| 340 | \\ --error-limit [num] Set the maximum amount of distinct error values | |
| 341 | \\ --build-file [file] Override path to build.zig | |
| 342 | \\ --cache-dir [path] Override path to local Zig cache directory | |
| 343 | \\ --global-cache-dir [path] Override path to global Zig cache directory | |
| 344 | \\ --zig-lib-dir [arg] Override path to Zig lib directory | |
| 345 | \\ --seed [integer] For shuffling dependency traversal order (default: random) | |
| 346 | \\ --cache-poison[=mode] Override configuration caching behavior | |
| 347 | \\ pure (default) Avoid false positive cache hits | |
| 348 | \\ poisoned Don't cache the configuration | |
| 349 | \\ disallowed Panics when cache would be poisoned | |
| 350 | \\ ignored A little poison never hurt anybody | |
| 351 | \\ --print-configuration Render configuration as .zon to stdout | |
| 352 | \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries | |
| 353 | \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM) | |
| 354 | \\ sha1, tree 20-byte cryptographic hash (ELF, WASM) | |
| 355 | \\ md5 16-byte cryptographic hash (ELF) | |
| 356 | \\ uuid 16-byte random UUID (ELF, WASM) | |
| 357 | \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM) | |
| 358 | \\ none (default) No build ID | |
| 359 | \\ --debug-log [scope] Enable debugging the compiler | |
| 360 | \\ --debug-pkg-config Fail if unknown pkg-config flags encountered | |
| 361 | \\ --maker-opt=[mode] Change maker executable optimization mode (default: ReleaseSafe) | |
| 362 | \\ --verbose-link Enable compiler debug output for linking | |
| 363 | \\ --verbose-air Enable compiler debug output for Zig AIR | |
| 364 | \\ --verbose-llvm-ir Enable compiler debug output for LLVM IR | |
| 365 | \\ --verbose-cimport Enable compiler debug output for C imports | |
| 366 | \\ --verbose-cc Enable compiler debug output for C compilation | |
| 367 | \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features | |
| 368 | \\ | |
| 369 | ); | |
| 370 | } |
lib/compiler/Maker/Step.zig created+863| ... | ... | @@ -0,0 +1,863 @@ |
| 1 | //! The *mutable* state that `Maker` needs in order to process one node from | |
| 2 | //! the build graph. | |
| 3 | const Step = @This(); | |
| 4 | ||
| 5 | const builtin = @import("builtin"); | |
| 6 | ||
| 7 | const std = @import("std"); | |
| 8 | const Allocator = std.mem.Allocator; | |
| 9 | const Cache = std.Build.Cache; | |
| 10 | const Io = std.Io; | |
| 11 | const Dir = std.Io.Dir; | |
| 12 | const LazyPath = std.Build.Configuration.LazyPath; | |
| 13 | const Package = std.Build.Configuration.Package; | |
| 14 | const Path = std.Build.Cache.Path; | |
| 15 | const Configuration = std.Build.Configuration; | |
| 16 | const assert = std.debug.assert; | |
| 17 | ||
| 18 | const WebServer = @import("WebServer.zig"); | |
| 19 | const Maker = @import("../Maker.zig"); | |
| 20 | ||
| 21 | pub const CheckFile = @import("Step/CheckFile.zig"); | |
| 22 | pub const Compile = @import("Step/Compile.zig"); | |
| 23 | pub const ConfigHeader = @import("Step/ConfigHeader.zig"); | |
| 24 | pub const FindProgram = @import("Step/FindProgram.zig"); | |
| 25 | pub const Fmt = @import("Step/Fmt.zig"); | |
| 26 | pub const InstallArtifact = @import("Step/InstallArtifact.zig"); | |
| 27 | pub const InstallDir = @import("Step/InstallDir.zig"); | |
| 28 | pub const InstallFile = @import("Step/InstallFile.zig"); | |
| 29 | pub const ObjCopy = @import("Step/ObjCopy.zig"); | |
| 30 | pub const Options = @import("Step/Options.zig"); | |
| 31 | pub const Run = @import("Step/Run.zig"); | |
| 32 | pub const TranslateC = @import("Step/TranslateC.zig"); | |
| 33 | pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig"); | |
| 34 | pub const WriteFile = @import("Step/WriteFile.zig"); | |
| 35 | ||
| 36 | /// Avoid false sharing. | |
| 37 | _: void align(std.atomic.cache_line) = {}, | |
| 38 | ||
| 39 | /// Extra data for specific types of steps. | |
| 40 | extended: Extended, | |
| 41 | ||
| 42 | /// This field is atomically accessed multi-threaded. | |
| 43 | state: State = .precheck_unstarted, | |
| 44 | ||
| 45 | dependants: std.ArrayList(Configuration.Step.Index) = .empty, | |
| 46 | /// Collects the set of files that retrigger this step to run. | |
| 47 | /// | |
| 48 | /// This is used by the build system's implementation of `--watch` but it can | |
| 49 | /// also be potentially useful for IDEs to know what effects editing a | |
| 50 | /// particular file has. | |
| 51 | /// | |
| 52 | /// Populated within `make`. Implementation may choose to clear and repopulate, | |
| 53 | /// retain previous value, or update. | |
| 54 | inputs: Inputs = .init, | |
| 55 | pending_deps: u32 = undefined, | |
| 56 | ||
| 57 | result_error_msgs: std.ArrayList([]const u8) = .empty, | |
| 58 | result_error_bundle: std.zig.ErrorBundle = .empty, | |
| 59 | result_stderr: []const u8 = "", | |
| 60 | result_cached: bool = false, | |
| 61 | /// Indicates error information is missing due to allocation failure. | |
| 62 | result_oom: bool = false, | |
| 63 | result_duration_ns: ?u64 = null, | |
| 64 | /// 0 means unavailable or not reported. | |
| 65 | result_peak_rss: usize = 0, | |
| 66 | /// If the step is failed and this field is populated, this is the command which failed. | |
| 67 | /// This field may be populated even if the step succeeded. | |
| 68 | /// Memory owned by `Maker.gpa`. | |
| 69 | result_failed_command: ?[]const u8 = null, | |
| 70 | test_results: TestResults = .{}, | |
| 71 | ||
| 72 | comptime { | |
| 73 | // Common cache line size is 128. This check prevents accidentally crossing | |
| 74 | // an additional cache line. In the future it might be nice to try to fit | |
| 75 | // this struct in 128 bytes or less. | |
| 76 | if (std.atomic.cache_line <= 128) assert(@sizeOf(@This()) <= 128 * 3); | |
| 77 | } | |
| 78 | ||
| 79 | pub const Extended = union(enum) { | |
| 80 | check_file: CheckFile, | |
| 81 | compile: Compile, | |
| 82 | config_header: ConfigHeader, | |
| 83 | fail: Fail, | |
| 84 | find_program: FindProgram, | |
| 85 | fmt: Fmt, | |
| 86 | install_artifact: InstallArtifact, | |
| 87 | install_dir: InstallDir, | |
| 88 | install_file: InstallFile, | |
| 89 | obj_copy: ObjCopy, | |
| 90 | options: Options, | |
| 91 | run: Run, | |
| 92 | top_level: TopLevel, | |
| 93 | translate_c: TranslateC, | |
| 94 | update_source_files: UpdateSourceFiles, | |
| 95 | write_file: WriteFile, | |
| 96 | ||
| 97 | pub fn init(tag: Configuration.Step.Tag) Extended { | |
| 98 | return switch (tag) { | |
| 99 | .check_file => .{ .check_file = .{} }, | |
| 100 | .compile => .{ .compile = .{} }, | |
| 101 | .config_header => .{ .config_header = .{} }, | |
| 102 | .fail => .{ .fail = .{} }, | |
| 103 | .find_program => .{ .find_program = .{} }, | |
| 104 | .fmt => .{ .fmt = .{} }, | |
| 105 | .install_artifact => .{ .install_artifact = .{} }, | |
| 106 | .install_dir => .{ .install_dir = .{} }, | |
| 107 | .install_file => .{ .install_file = .{} }, | |
| 108 | .obj_copy => .{ .obj_copy = .{} }, | |
| 109 | .options => .{ .options = .{} }, | |
| 110 | .run => .{ .run = .{} }, | |
| 111 | .top_level => .{ .top_level = .{} }, | |
| 112 | .translate_c => .{ .translate_c = .{} }, | |
| 113 | .update_source_files => .{ .update_source_files = .{} }, | |
| 114 | .write_file => .{ .write_file = .{} }, | |
| 115 | }; | |
| 116 | } | |
| 117 | ||
| 118 | pub const TopLevel = struct { | |
| 119 | pub fn make( | |
| 120 | top_level: *TopLevel, | |
| 121 | step_index: Configuration.Step.Index, | |
| 122 | maker: *Maker, | |
| 123 | progress_node: std.Progress.Node, | |
| 124 | ) Step.ExtendedMakeError!void { | |
| 125 | _ = top_level; | |
| 126 | _ = step_index; | |
| 127 | _ = maker; | |
| 128 | _ = progress_node; | |
| 129 | } | |
| 130 | }; | |
| 131 | ||
| 132 | pub const Fail = struct { | |
| 133 | pub fn make( | |
| 134 | this: *@This(), | |
| 135 | step_index: Configuration.Step.Index, | |
| 136 | maker: *Maker, | |
| 137 | progress_node: std.Progress.Node, | |
| 138 | ) Step.ExtendedMakeError!void { | |
| 139 | _ = this; | |
| 140 | _ = progress_node; | |
| 141 | const graph = maker.graph; | |
| 142 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 143 | const conf = &maker.scanned_config.configuration; | |
| 144 | const step = maker.stepByIndex(step_index); | |
| 145 | const conf_step = step_index.ptr(conf); | |
| 146 | const conf_fail = conf_step.extended.get(conf.extra).fail; | |
| 147 | ||
| 148 | try step.result_error_msgs.append(arena, conf_fail.msg.slice(conf)); | |
| 149 | return error.MakeFailed; | |
| 150 | } | |
| 151 | }; | |
| 152 | }; | |
| 153 | ||
| 154 | pub const State = enum { | |
| 155 | precheck_unstarted, | |
| 156 | precheck_started, | |
| 157 | /// This is also used to indicate "dirty" steps that have been modified | |
| 158 | /// after a previous build completed, in which case, the step may or may | |
| 159 | /// not have been completed before. Either way, one or more of its direct | |
| 160 | /// file system inputs have been modified, meaning that the step needs to | |
| 161 | /// be re-evaluated. | |
| 162 | precheck_done, | |
| 163 | dependency_failure, | |
| 164 | success, | |
| 165 | failure, | |
| 166 | /// This state indicates that the step did not complete, however, it also did not fail, | |
| 167 | /// and it is safe to continue executing its dependencies. | |
| 168 | skipped, | |
| 169 | /// This step was skipped because it specified a max_rss that exceeded the runner's maximum. | |
| 170 | /// It is not safe to run its dependencies. | |
| 171 | skipped_oom, | |
| 172 | }; | |
| 173 | ||
| 174 | pub const Inputs = struct { | |
| 175 | table: Table, | |
| 176 | ||
| 177 | pub const init: Inputs = .{ | |
| 178 | .table = .{}, | |
| 179 | }; | |
| 180 | ||
| 181 | pub const Table = std.ArrayHashMapUnmanaged(Path, Files, Path.TableAdapter, false); | |
| 182 | /// The special file name "." means any changes inside the directory. | |
| 183 | pub const Files = std.ArrayList([]const u8); | |
| 184 | ||
| 185 | pub fn populated(inputs: *Inputs) bool { | |
| 186 | return inputs.table.count() != 0; | |
| 187 | } | |
| 188 | ||
| 189 | pub fn clear(inputs: *Inputs, gpa: Allocator) void { | |
| 190 | for (inputs.table.values()) |*files| files.deinit(gpa); | |
| 191 | inputs.table.clearRetainingCapacity(); | |
| 192 | } | |
| 193 | ||
| 194 | pub fn deinit(inputs: *Inputs, gpa: Allocator) void { | |
| 195 | clear(inputs, gpa); | |
| 196 | inputs.table.deinit(gpa); | |
| 197 | } | |
| 198 | }; | |
| 199 | ||
| 200 | pub const TestResults = struct { | |
| 201 | /// The total number of tests in the step. Every test has a "status" from the following: | |
| 202 | /// * passed | |
| 203 | /// * skipped | |
| 204 | /// * failed cleanly | |
| 205 | /// * crashed | |
| 206 | /// * timed out | |
| 207 | test_count: u32 = 0, | |
| 208 | ||
| 209 | /// The number of tests which were skipped (`error.SkipZigTest`). | |
| 210 | skip_count: u32 = 0, | |
| 211 | /// The number of tests which failed cleanly. | |
| 212 | fail_count: u32 = 0, | |
| 213 | /// The number of tests which terminated unexpectedly, i.e. crashed. | |
| 214 | crash_count: u32 = 0, | |
| 215 | /// The number of tests which timed out. | |
| 216 | timeout_count: u32 = 0, | |
| 217 | ||
| 218 | /// The number of detected memory leaks. The associated test may still have passed; indeed, *all* | |
| 219 | /// individual tests may have passed. However, the step as a whole fails if any test has leaks. | |
| 220 | leak_count: u32 = 0, | |
| 221 | /// The number of detected error logs. The associated test may still have passed; indeed, *all* | |
| 222 | /// individual tests may have passed. However, the step as a whole fails if any test logs errors. | |
| 223 | log_err_count: u32 = 0, | |
| 224 | ||
| 225 | pub fn isSuccess(tr: TestResults) bool { | |
| 226 | // all steps are success or skip | |
| 227 | return tr.fail_count == 0 and | |
| 228 | tr.crash_count == 0 and | |
| 229 | tr.timeout_count == 0 and | |
| 230 | // no (otherwise successful) step leaked memory or logged errors | |
| 231 | tr.leak_count == 0 and | |
| 232 | tr.log_err_count == 0; | |
| 233 | } | |
| 234 | ||
| 235 | /// Computes the number of tests which passed from the other values. | |
| 236 | pub fn passCount(tr: TestResults) u32 { | |
| 237 | return tr.test_count - tr.skip_count - tr.fail_count - tr.crash_count - tr.timeout_count; | |
| 238 | } | |
| 239 | }; | |
| 240 | ||
| 241 | pub const MakeError = error{ | |
| 242 | /// Indicates the error is already reported. | |
| 243 | MakeFailed, | |
| 244 | MakeSkipped, | |
| 245 | } || Io.Cancelable; | |
| 246 | ||
| 247 | pub const ExtendedMakeError = MakeError || Allocator.Error; | |
| 248 | ||
| 249 | pub fn make( | |
| 250 | step_index: Configuration.Step.Index, | |
| 251 | maker: *Maker, | |
| 252 | progress_node: std.Progress.Node, | |
| 253 | ) MakeError!void { | |
| 254 | const graph = maker.graph; | |
| 255 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 256 | const io = graph.io; | |
| 257 | const c = &maker.scanned_config.configuration; | |
| 258 | const conf_step = step_index.ptr(c); | |
| 259 | const s = maker.stepByIndex(step_index); | |
| 260 | ||
| 261 | var start_ts: ?Io.Timestamp = t: { | |
| 262 | if (!graph.time_report) break :t null; | |
| 263 | const flags = conf_step.flags(c); | |
| 264 | switch (flags.tag) { | |
| 265 | .compile => break :t null, | |
| 266 | .run => { | |
| 267 | const run_flags: Configuration.Step.Run.Flags = @bitCast(flags); | |
| 268 | if (run_flags.stdio == .zig_test) break :t null; | |
| 269 | }, | |
| 270 | else => {}, | |
| 271 | } | |
| 272 | break :t Io.Clock.awake.now(io); | |
| 273 | }; | |
| 274 | const make_result = switch (s.extended) { | |
| 275 | inline else => |*extended| extended.make(step_index, maker, progress_node), | |
| 276 | }; | |
| 277 | if (start_ts) |*ts| { | |
| 278 | const duration = ts.untilNow(io, .awake); | |
| 279 | maker.web_server.?.updateTimeReportGeneric(step_index, duration); | |
| 280 | } | |
| 281 | ||
| 282 | make_result catch |err| switch (err) { | |
| 283 | error.MakeFailed, error.MakeSkipped => |e| return e, | |
| 284 | error.OutOfMemory => { | |
| 285 | s.result_oom = true; | |
| 286 | return error.MakeFailed; | |
| 287 | }, | |
| 288 | error.Canceled => |e| return e, | |
| 289 | }; | |
| 290 | ||
| 291 | if (!s.test_results.isSuccess()) { | |
| 292 | return error.MakeFailed; | |
| 293 | } | |
| 294 | ||
| 295 | const max_rss = conf_step.max_rss.toBytes(); | |
| 296 | if (max_rss != 0 and s.result_peak_rss > max_rss) { | |
| 297 | if (std.fmt.allocPrint( | |
| 298 | arena, | |
| 299 | "memory usage peaked at {0B:.2} ({0d} bytes), exceeding the declared upper bound of {1B:.2} ({1d} bytes)", | |
| 300 | .{ s.result_peak_rss, max_rss }, | |
| 301 | )) |msg| { | |
| 302 | s.oomWrap(s.result_error_msgs.append(arena, msg)); | |
| 303 | } else |_| s.result_oom = true; | |
| 304 | } | |
| 305 | } | |
| 306 | ||
| 307 | /// Prepares the step for being re-evaluated. | |
| 308 | pub fn reset(step: *Step, maker: *Maker) void { | |
| 309 | assert(step.state == .precheck_done); | |
| 310 | const gpa = maker.gpa; | |
| 311 | ||
| 312 | clearFailedCommand(step, gpa); | |
| 313 | ||
| 314 | step.result_error_msgs.clearRetainingCapacity(); | |
| 315 | step.result_stderr = ""; | |
| 316 | step.result_cached = false; | |
| 317 | step.result_duration_ns = null; | |
| 318 | step.result_peak_rss = 0; | |
| 319 | step.test_results = .{}; | |
| 320 | clearWatchInputs(step, maker); | |
| 321 | clearErrorBundle(step, gpa); | |
| 322 | } | |
| 323 | ||
| 324 | pub const CaptureChildProcessError = error{ | |
| 325 | FileNotFound, | |
| 326 | } || ExtendedMakeError; | |
| 327 | ||
| 328 | pub const CaptureChildProcessOptions = struct { | |
| 329 | argv: []const []const u8, | |
| 330 | progress_node: std.Progress.Node = .none, | |
| 331 | environ_map: ?*const std.process.Environ.Map = null, | |
| 332 | allow_failure: bool = false, | |
| 333 | }; | |
| 334 | ||
| 335 | /// Populates `s.result_failed_command`. | |
| 336 | pub fn captureChildProcess(s: *Step, maker: *Maker, options: CaptureChildProcessOptions) !std.process.RunResult { | |
| 337 | const gpa = maker.gpa; | |
| 338 | const graph = maker.graph; | |
| 339 | const arena = graph.arena; // TODO stop leaking into process arena | |
| 340 | const io = graph.io; | |
| 341 | ||
| 342 | clearFailedCommand(s, gpa); | |
| 343 | s.result_failed_command = try std.zig.allocPrintCmd(gpa, options.argv, .{}); | |
| 344 | ||
| 345 | try handleChildProcUnsupported(s, maker); | |
| 346 | try graph.handleVerbose(null, null, options.argv); | |
| 347 | ||
| 348 | const result = std.process.run(arena, io, .{ | |
| 349 | .argv = options.argv, | |
| 350 | .environ_map = options.environ_map orelse &graph.environ_map, | |
| 351 | .progress_node = options.progress_node, | |
| 352 | }) catch |err| { | |
| 353 | switch (err) { | |
| 354 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 355 | error.FileNotFound => |e| if (options.allow_failure) return e, | |
| 356 | else => {}, | |
| 357 | } | |
| 358 | return s.fail(maker, "failed to run {s}: {t}", .{ options.argv[0], err }); | |
| 359 | }; | |
| 360 | ||
| 361 | if (result.stderr.len > 0) try s.result_error_msgs.append(arena, result.stderr); | |
| 362 | ||
| 363 | return result; | |
| 364 | } | |
| 365 | ||
| 366 | pub fn clearErrorBundle(s: *Step, gpa: Allocator) void { | |
| 367 | s.result_error_bundle.deinit(gpa); | |
| 368 | s.result_error_bundle = .empty; | |
| 369 | } | |
| 370 | ||
| 371 | pub fn clearFailedCommand(s: *Step, gpa: Allocator) void { | |
| 372 | if (s.result_failed_command) |cmd| { | |
| 373 | gpa.free(cmd); | |
| 374 | s.result_failed_command = null; | |
| 375 | } | |
| 376 | } | |
| 377 | ||
| 378 | pub const FailError = error{ OutOfMemory, MakeFailed }; | |
| 379 | ||
| 380 | pub fn fail(step: *Step, maker: *const Maker, comptime fmt: []const u8, args: anytype) FailError { | |
| 381 | try step.addError(maker, fmt, args); | |
| 382 | return error.MakeFailed; | |
| 383 | } | |
| 384 | ||
| 385 | pub fn addError(step: *Step, maker: *const Maker, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { | |
| 386 | const graph = maker.graph; | |
| 387 | const arena = graph.arena; // TODO don't leak into the process_arena | |
| 388 | const msg = try std.fmt.allocPrint(arena, fmt, args); | |
| 389 | try step.result_error_msgs.append(arena, msg); | |
| 390 | } | |
| 391 | ||
| 392 | pub const ZigProcess = struct { | |
| 393 | child: std.process.Child, | |
| 394 | multi_reader_buffer: Io.File.MultiReader.Buffer(2), | |
| 395 | multi_reader: Io.File.MultiReader, | |
| 396 | progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn, | |
| 397 | ||
| 398 | pub const StreamEnum = enum { stdout, stderr }; | |
| 399 | ||
| 400 | pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void { | |
| 401 | zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null; | |
| 402 | } | |
| 403 | ||
| 404 | pub fn deinit(zp: *ZigProcess, io: Io) void { | |
| 405 | zp.child.kill(io); | |
| 406 | zp.multi_reader.deinit(); | |
| 407 | zp.* = undefined; | |
| 408 | } | |
| 409 | }; | |
| 410 | ||
| 411 | /// Assumes that argv contains `--listen=-` and that the process being spawned | |
| 412 | /// is the zig compiler - the same version that compiled the build runner. | |
| 413 | /// Populates `s.result_failed_command`. | |
| 414 | pub fn evalZigProcess( | |
| 415 | step_index: Configuration.Step.Index, | |
| 416 | maker: *Maker, | |
| 417 | argv: []const []const u8, | |
| 418 | prog_node: std.Progress.Node, | |
| 419 | watch: bool, | |
| 420 | ) (Step.ExtendedMakeError || error{NeedCompileErrorCheck})!?Path { | |
| 421 | const s = maker.stepByIndex(step_index); | |
| 422 | const gpa = maker.gpa; | |
| 423 | const graph = maker.graph; | |
| 424 | const io = graph.io; | |
| 425 | ||
| 426 | // If an error occurs, it's happened in this command: | |
| 427 | clearFailedCommand(s, gpa); | |
| 428 | s.result_failed_command = try std.zig.allocPrintCmd(gpa, argv, .{}); | |
| 429 | ||
| 430 | if (s.getZigProcess()) |zp| update: { | |
| 431 | assert(watch); | |
| 432 | if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index); | |
| 433 | zp.progress_ipc_index = null; | |
| 434 | var exited = false; | |
| 435 | defer if (exited) { | |
| 436 | s.extended.compile.zig_process = null; | |
| 437 | zp.deinit(io); | |
| 438 | gpa.destroy(zp); | |
| 439 | } else zp.saveState(prog_node); | |
| 440 | const result = zigProcessUpdate(step_index, maker, zp, watch) catch |err| switch (err) { | |
| 441 | error.BrokenPipe, error.EndOfStream => |reason| { | |
| 442 | // Process restart required. | |
| 443 | std.log.info("{s} restart required: {t}", .{ argv[0], reason }); | |
| 444 | _ = zp.child.wait(io) catch |e| return s.fail(maker, "unable to wait for {s}: {t}", .{ argv[0], e }); | |
| 445 | exited = true; | |
| 446 | break :update; | |
| 447 | }, | |
| 448 | error.OutOfMemory, error.Canceled, error.MakeFailed => |e| return e, | |
| 449 | else => |e| return s.fail(maker, "zig child process monitoring failed: {t}", .{e}), | |
| 450 | }; | |
| 451 | ||
| 452 | if (s.result_error_bundle.errorMessageCount() > 0) | |
| 453 | return s.fail(maker, "{d} compilation errors", .{s.result_error_bundle.errorMessageCount()}); | |
| 454 | ||
| 455 | if (s.result_error_msgs.items.len > 0 and result == null) { | |
| 456 | // Crash detected. | |
| 457 | const term = zp.child.wait(io) catch |e| { | |
| 458 | return s.fail(maker, "unable to wait for {s}: {t}", .{ argv[0], e }); | |
| 459 | }; | |
| 460 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 461 | exited = true; | |
| 462 | try handleChildProcessTerm(s, maker, term); | |
| 463 | return error.MakeFailed; | |
| 464 | } | |
| 465 | ||
| 466 | return result; | |
| 467 | } | |
| 468 | assert(argv.len != 0); | |
| 469 | ||
| 470 | try handleChildProcUnsupported(s, maker); | |
| 471 | try graph.handleVerbose(null, null, argv); | |
| 472 | ||
| 473 | const zp = try gpa.create(ZigProcess); | |
| 474 | defer if (!watch) gpa.destroy(zp); | |
| 475 | ||
| 476 | zp.child = std.process.spawn(io, .{ | |
| 477 | .argv = argv, | |
| 478 | .environ_map = &graph.environ_map, | |
| 479 | .stdin = .pipe, | |
| 480 | .stdout = .pipe, | |
| 481 | .stderr = .pipe, | |
| 482 | .request_resource_usage_statistics = true, | |
| 483 | .progress_node = prog_node, | |
| 484 | }) catch |err| return s.fail(maker, "failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); | |
| 485 | ||
| 486 | zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{ | |
| 487 | zp.child.stdout.?, zp.child.stderr.?, | |
| 488 | }); | |
| 489 | if (watch) s.extended.compile.zig_process = zp; | |
| 490 | defer if (!watch) zp.deinit(io); | |
| 491 | ||
| 492 | const result = result: { | |
| 493 | defer if (watch) zp.saveState(prog_node); | |
| 494 | break :result zigProcessUpdate(step_index, maker, zp, watch) catch |err| switch (err) { | |
| 495 | error.OutOfMemory, error.Canceled, error.MakeFailed => |e| return e, | |
| 496 | else => |e| return s.fail(maker, "zig child process monitoring failed: {t}", .{e}), | |
| 497 | }; | |
| 498 | }; | |
| 499 | ||
| 500 | if (!watch) { | |
| 501 | // Send EOF to stdin. | |
| 502 | zp.child.stdin.?.close(io); | |
| 503 | zp.child.stdin = null; | |
| 504 | ||
| 505 | const term = zp.child.wait(io) catch |err| { | |
| 506 | return s.fail(maker, "unable to wait for {s}: {t}", .{ argv[0], err }); | |
| 507 | }; | |
| 508 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 509 | ||
| 510 | // Special handling for compile step that is expecting compile errors. | |
| 511 | const conf = &maker.scanned_config.configuration; | |
| 512 | if (term == .exited) switch (step_index.ptr(conf).extended.get(conf.extra)) { | |
| 513 | .compile => |compile| if (compile.flags4.expect_errors != .none) { | |
| 514 | // Note that the exit code may be 0 in this case due to the | |
| 515 | // compiler server protocol. | |
| 516 | return error.NeedCompileErrorCheck; | |
| 517 | }, | |
| 518 | else => {}, | |
| 519 | }; | |
| 520 | try handleChildProcessTerm(s, maker, term); | |
| 521 | } | |
| 522 | ||
| 523 | if (s.result_error_bundle.errorMessageCount() > 0) { | |
| 524 | return s.fail(maker, "{d} compilation errors", .{s.result_error_bundle.errorMessageCount()}); | |
| 525 | } | |
| 526 | ||
| 527 | return result; | |
| 528 | } | |
| 529 | ||
| 530 | fn zigProcessUpdate(step_index: Configuration.Step.Index, maker: *Maker, zp: *ZigProcess, watch: bool) !?Path { | |
| 531 | const s = maker.stepByIndex(step_index); | |
| 532 | const gpa = maker.gpa; | |
| 533 | const graph = maker.graph; | |
| 534 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 535 | const io = graph.io; | |
| 536 | ||
| 537 | const start_ts = Io.Clock.awake.now(io); | |
| 538 | ||
| 539 | try sendMessage(io, zp.child.stdin.?, .update); | |
| 540 | if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); | |
| 541 | ||
| 542 | var result: ?Path = null; | |
| 543 | var eos_err: error{EndOfStream}!void = {}; | |
| 544 | ||
| 545 | const stdout = zp.multi_reader.fileReader(0); | |
| 546 | ||
| 547 | while (true) { | |
| 548 | const Header = std.zig.Server.Message.Header; | |
| 549 | const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { | |
| 550 | error.EndOfStream => break, | |
| 551 | error.ReadFailed => return stdout.err.?, | |
| 552 | }; | |
| 553 | const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { | |
| 554 | error.EndOfStream => |e| { | |
| 555 | // Better to report the crash with stderr below, but we set | |
| 556 | // this in case the child exits successfully while violating | |
| 557 | // this protocol. | |
| 558 | eos_err = e; | |
| 559 | break; | |
| 560 | }, | |
| 561 | error.ReadFailed => return stdout.err.?, | |
| 562 | }; | |
| 563 | switch (header.tag) { | |
| 564 | .zig_version => { | |
| 565 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { | |
| 566 | return s.fail( | |
| 567 | maker, | |
| 568 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 569 | .{ builtin.zig_version_string, body }, | |
| 570 | ); | |
| 571 | } | |
| 572 | }, | |
| 573 | .error_bundle => { | |
| 574 | s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); | |
| 575 | // This message indicates the end of the update. | |
| 576 | if (watch) break; | |
| 577 | }, | |
| 578 | .emit_digest => { | |
| 579 | const EmitDigest = std.zig.Server.Message.EmitDigest; | |
| 580 | const emit_digest: *align(1) const EmitDigest = @ptrCast(body); | |
| 581 | s.result_cached = emit_digest.flags.cache_hit; | |
| 582 | const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len]; | |
| 583 | result = .{ | |
| 584 | .root_dir = graph.local_cache_root, | |
| 585 | .sub_path = try arena.dupe(u8, "o" ++ Dir.path.sep_str ++ Cache.binToHex(digest.*)), | |
| 586 | }; | |
| 587 | }, | |
| 588 | .file_system_inputs => { | |
| 589 | clearWatchInputs(s, maker); | |
| 590 | const conf = &maker.scanned_config.configuration; | |
| 591 | const conf_step = step_index.ptr(conf); | |
| 592 | var it = std.mem.splitScalar(u8, body, 0); | |
| 593 | while (it.next()) |prefixed_path| { | |
| 594 | const prefix_index: std.zig.Server.Message.PathPrefix = @enumFromInt(prefixed_path[0] - 1); | |
| 595 | const sub_path = try arena.dupe(u8, prefixed_path[1..]); | |
| 596 | const sub_path_dirname = Dir.path.dirname(sub_path) orelse ""; | |
| 597 | switch (prefix_index) { | |
| 598 | .cwd => { | |
| 599 | const path: Path = .{ | |
| 600 | .root_dir = .cwd(), | |
| 601 | .sub_path = sub_path_dirname, | |
| 602 | }; | |
| 603 | try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path)); | |
| 604 | }, | |
| 605 | .zig_lib => zl: { | |
| 606 | switch (conf_step.extended.get(conf.extra)) { | |
| 607 | .compile => |compile| if (compile.zig_lib_dir.value) |zig_lib_dir| { | |
| 608 | const resolved = try maker.resolveLazyPathIndex(arena, zig_lib_dir, step_index); | |
| 609 | const appended = try resolved.join(arena, sub_path); | |
| 610 | try addWatchInputPath(s, maker, appended); | |
| 611 | break :zl; | |
| 612 | }, | |
| 613 | else => {}, | |
| 614 | } | |
| 615 | const path: Path = .{ | |
| 616 | .root_dir = graph.zig_lib_directory, | |
| 617 | .sub_path = sub_path_dirname, | |
| 618 | }; | |
| 619 | try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path)); | |
| 620 | }, | |
| 621 | .local_cache => { | |
| 622 | const path: Path = .{ | |
| 623 | .root_dir = graph.local_cache_root, | |
| 624 | .sub_path = sub_path_dirname, | |
| 625 | }; | |
| 626 | try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path)); | |
| 627 | }, | |
| 628 | .global_cache => { | |
| 629 | const path: Path = .{ | |
| 630 | .root_dir = graph.global_cache_root, | |
| 631 | .sub_path = sub_path_dirname, | |
| 632 | }; | |
| 633 | try addWatchInputFromPath(s, maker, path, Dir.path.basename(sub_path)); | |
| 634 | }, | |
| 635 | } | |
| 636 | } | |
| 637 | }, | |
| 638 | .time_report => if (maker.web_server) |*ws| { | |
| 639 | const TimeReport = std.zig.Server.Message.TimeReport; | |
| 640 | const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]); | |
| 641 | ws.updateTimeReportCompile(.{ | |
| 642 | .compile_step = step_index, | |
| 643 | .use_llvm = tr.flags.use_llvm, | |
| 644 | .stats = tr.stats, | |
| 645 | .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()), | |
| 646 | .llvm_pass_timings_len = tr.llvm_pass_timings_len, | |
| 647 | .files_len = tr.files_len, | |
| 648 | .decls_len = tr.decls_len, | |
| 649 | .trailing = body[@sizeOf(TimeReport)..], | |
| 650 | }); | |
| 651 | }, | |
| 652 | else => {}, // ignore other messages | |
| 653 | } | |
| 654 | } | |
| 655 | ||
| 656 | s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()); | |
| 657 | ||
| 658 | const stderr_contents = zp.multi_reader.reader(1).buffered(); | |
| 659 | if (stderr_contents.len > 0) { | |
| 660 | try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); | |
| 661 | } | |
| 662 | ||
| 663 | try eos_err; | |
| 664 | ||
| 665 | return result; | |
| 666 | } | |
| 667 | ||
| 668 | pub fn getZigProcess(s: *Step) ?*ZigProcess { | |
| 669 | return switch (s.extended) { | |
| 670 | .compile => |*compile| compile.zig_process, | |
| 671 | else => null, | |
| 672 | }; | |
| 673 | } | |
| 674 | ||
| 675 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | |
| 676 | const header: std.zig.Client.Message.Header = .{ | |
| 677 | .tag = tag, | |
| 678 | .bytes_len = 0, | |
| 679 | }; | |
| 680 | var w = file.writer(io, &.{}); | |
| 681 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 682 | error.WriteFailed => return w.err.?, | |
| 683 | }; | |
| 684 | } | |
| 685 | ||
| 686 | /// Asserts that the caller has already populated `s.result_failed_command`. | |
| 687 | pub inline fn handleChildProcUnsupported(s: *Step, maker: *Maker) FailError!void { | |
| 688 | assert(s.result_failed_command != null); | |
| 689 | if (!std.process.can_spawn) | |
| 690 | return s.fail(maker, "unable to spawn process: host cannot spawn child processes", .{}); | |
| 691 | } | |
| 692 | ||
| 693 | /// Asserts that the caller has already populated `s.result_failed_command`. | |
| 694 | pub fn handleChildProcessTerm(s: *Step, maker: *Maker, term: std.process.Child.Term) FailError!void { | |
| 695 | assert(s.result_failed_command != null); | |
| 696 | if (!term.success()) return s.fail(maker, "process {f}", .{term}); | |
| 697 | } | |
| 698 | ||
| 699 | /// Prefer `cacheHitAndWatch` unless you already added watch inputs | |
| 700 | /// separately from using the cache system. | |
| 701 | pub fn cacheHit(s: *Step, maker: *Maker, man: *Cache.Manifest) !bool { | |
| 702 | s.result_cached = man.hit() catch |err| return failWithCacheError(s, maker, man, err); | |
| 703 | return s.result_cached; | |
| 704 | } | |
| 705 | ||
| 706 | /// Clears previous watch inputs, if any, and then populates watch inputs from | |
| 707 | /// the full set of files picked up by the cache manifest. | |
| 708 | /// | |
| 709 | /// Must be accompanied with `writeManifestAndWatch`. | |
| 710 | pub fn cacheHitAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !bool { | |
| 711 | const is_hit = man.hit() catch |err| return failWithCacheError(s, maker, man, err); | |
| 712 | s.result_cached = is_hit; | |
| 713 | // The above call to hit() populates the manifest with files, so in case of | |
| 714 | // a hit, we need to populate watch inputs. | |
| 715 | if (is_hit) try setWatchInputsFromManifest(s, maker, man); | |
| 716 | return is_hit; | |
| 717 | } | |
| 718 | ||
| 719 | fn failWithCacheError( | |
| 720 | s: *Step, | |
| 721 | maker: *Maker, | |
| 722 | man: *const Cache.Manifest, | |
| 723 | err: Cache.Manifest.HitError, | |
| 724 | ) error{ OutOfMemory, Canceled, MakeFailed } { | |
| 725 | switch (err) { | |
| 726 | error.CacheCheckFailed => switch (man.diagnostic) { | |
| 727 | .none => unreachable, | |
| 728 | .manifest_create, .manifest_read, .manifest_lock => |e| return s.fail(maker, "failed checking cache: {t} {t}", .{ | |
| 729 | man.diagnostic, e, | |
| 730 | }), | |
| 731 | .file_open, .file_stat, .file_read, .file_hash => |op| { | |
| 732 | const pp = man.files.keys()[op.file_index].prefixed_path; | |
| 733 | const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; | |
| 734 | return s.fail(maker, "failed checking cache: {s}{c}{s} {t} {t}", .{ | |
| 735 | prefix, Dir.path.sep, pp.sub_path, man.diagnostic, op.err, | |
| 736 | }); | |
| 737 | }, | |
| 738 | }, | |
| 739 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 740 | error.InvalidFormat => return s.fail(maker, "failed checking cache: invalid manifest file format", .{}), | |
| 741 | } | |
| 742 | } | |
| 743 | ||
| 744 | /// Prefer `writeManifestAndWatch` unless you already added watch inputs | |
| 745 | /// separately from using the cache system. | |
| 746 | pub fn writeManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void { | |
| 747 | if (s.test_results.isSuccess()) { | |
| 748 | man.writeManifest() catch |err| switch (err) { | |
| 749 | error.Canceled => |e| return e, | |
| 750 | else => |e| try s.addError(maker, "failed writing cache manifest: {t}", .{e}), | |
| 751 | }; | |
| 752 | } | |
| 753 | } | |
| 754 | ||
| 755 | /// Clears previous watch inputs, if any, and then populates watch inputs from | |
| 756 | /// the full set of files picked up by the cache manifest. | |
| 757 | /// | |
| 758 | /// Must be accompanied with `cacheHitAndWatch`. | |
| 759 | pub fn writeManifestAndWatch(s: *Step, maker: *Maker, man: *Cache.Manifest) !void { | |
| 760 | try writeManifest(s, maker, man); | |
| 761 | try setWatchInputsFromManifest(s, maker, man); | |
| 762 | } | |
| 763 | ||
| 764 | fn setWatchInputsFromManifest(s: *Step, maker: *Maker, man: *Cache.Manifest) !void { | |
| 765 | const graph = maker.graph; | |
| 766 | const arena = graph.arena; // TODO don't leak into process arena | |
| 767 | const prefixes = man.cache.prefixes(); | |
| 768 | clearWatchInputs(s, maker); | |
| 769 | for (man.files.keys()) |file| { | |
| 770 | // The file path data is freed when the cache manifest is cleaned up at the end of `make`. | |
| 771 | const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path); | |
| 772 | try addWatchInputFromPath(s, maker, .{ | |
| 773 | .root_dir = prefixes[file.prefixed_path.prefix], | |
| 774 | .sub_path = Dir.path.dirname(sub_path) orelse "", | |
| 775 | }, Dir.path.basename(sub_path)); | |
| 776 | } | |
| 777 | } | |
| 778 | ||
| 779 | /// For steps that have a single input that never changes when re-running `make`. | |
| 780 | pub fn singleUnchangingWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_path: LazyPath) Allocator.Error!void { | |
| 781 | if (!step.inputs.populated()) try step.addWatchInput(maker, arena, lazy_path); | |
| 782 | } | |
| 783 | ||
| 784 | pub fn clearWatchInputs(step: *Step, maker: *Maker) void { | |
| 785 | step.inputs.clear(maker.gpa); | |
| 786 | } | |
| 787 | ||
| 788 | /// Places a *file* dependency on the path. | |
| 789 | pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: LazyPath) Allocator.Error!void { | |
| 790 | const conf = &maker.scanned_config.configuration; | |
| 791 | switch (lazy_file) { | |
| 792 | .source_path => |source_path| { | |
| 793 | const sub_path = source_path.sub_path.slice(conf); | |
| 794 | const pkg_path = try maker.packagePath(arena, source_path.owner, sub_path); | |
| 795 | try addWatchInputPath(step, maker, pkg_path); | |
| 796 | }, | |
| 797 | .relative => |relative| { | |
| 798 | const resolved_path = try maker.relativePath(arena, relative); | |
| 799 | try addWatchInputPath(step, maker, resolved_path); | |
| 800 | }, | |
| 801 | // Nothing to watch because this dependency edge is modeled instead via `dependants`. | |
| 802 | .generated => {}, | |
| 803 | } | |
| 804 | } | |
| 805 | ||
| 806 | /// Any changes inside the directory will trigger invalidation. | |
| 807 | /// | |
| 808 | /// See also `addDirectoryWatchInputFromPath` which takes a `Path` instead. | |
| 809 | /// | |
| 810 | /// Paths derived from this directory should also be manually added via | |
| 811 | /// `addDirectoryWatchInputFromPath` if and only if this function returns | |
| 812 | /// `true`. | |
| 813 | pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) Allocator.Error!bool { | |
| 814 | const graph = maker.graph; | |
| 815 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 816 | switch (lazy_directory) { | |
| 817 | .source_path => |source_path| { | |
| 818 | const conf = &maker.scanned_config.configuration; | |
| 819 | const sub_path = source_path.sub_path.slice(conf); | |
| 820 | const pkg_path = try maker.packagePath(arena, source_path.owner, sub_path); | |
| 821 | try addDirectoryWatchInputFromPath(step, maker, pkg_path); | |
| 822 | }, | |
| 823 | .relative => |relative| { | |
| 824 | const resolved_path = try maker.relativePath(arena, relative); | |
| 825 | try addDirectoryWatchInputFromPath(step, maker, resolved_path); | |
| 826 | }, | |
| 827 | // Nothing to watch because this dependency edge is modeled instead via `dependants`. | |
| 828 | .generated => return false, | |
| 829 | } | |
| 830 | return true; | |
| 831 | } | |
| 832 | ||
| 833 | /// Any changes inside the directory will trigger invalidation. | |
| 834 | /// | |
| 835 | /// See also `addDirectoryWatchInput` which takes a `LazyPath` instead. | |
| 836 | /// | |
| 837 | /// This function should only be called when it has been verified that the | |
| 838 | /// dependency on `path` is not already accounted for by a `Step` dependency. | |
| 839 | /// In other words, before calling this function, first check that the | |
| 840 | /// `LazyPath` which this `path` is derived from is not `generated`. | |
| 841 | pub fn addDirectoryWatchInputFromPath(step: *Step, maker: *Maker, path: Path) !void { | |
| 842 | return addWatchInputFromPath(step, maker, path, "."); | |
| 843 | } | |
| 844 | ||
| 845 | fn addWatchInputPath(step: *Step, maker: *Maker, path: Path) Allocator.Error!void { | |
| 846 | return addWatchInputFromPath(step, maker, .{ | |
| 847 | .root_dir = path.root_dir, | |
| 848 | .sub_path = Dir.path.dirname(path.sub_path) orelse "", | |
| 849 | }, Dir.path.basename(path.sub_path)); | |
| 850 | } | |
| 851 | ||
| 852 | fn addWatchInputFromPath(step: *Step, maker: *Maker, directory: Path, basename: []const u8) Allocator.Error!void { | |
| 853 | const gpa = maker.gpa; | |
| 854 | const gop = try step.inputs.table.getOrPut(gpa, directory); | |
| 855 | if (!gop.found_existing) gop.value_ptr.* = .empty; | |
| 856 | try gop.value_ptr.append(gpa, basename); | |
| 857 | } | |
| 858 | ||
| 859 | fn oomWrap(s: *Step, result: error{OutOfMemory}!void) void { | |
| 860 | result catch { | |
| 861 | s.result_oom = true; | |
| 862 | }; | |
| 863 | } |
lib/compiler/Maker/Step/CheckFile.zig created+63| ... | ... | @@ -0,0 +1,63 @@ |
| 1 | const CheckFile = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Configuration = std.Build.Configuration; | |
| 6 | ||
| 7 | const Step = @import("../Step.zig"); | |
| 8 | const Maker = @import("../../Maker.zig"); | |
| 9 | ||
| 10 | pub fn make( | |
| 11 | check_file: *CheckFile, | |
| 12 | step_index: Configuration.Step.Index, | |
| 13 | maker: *Maker, | |
| 14 | progress_node: std.Progress.Node, | |
| 15 | ) Step.ExtendedMakeError!void { | |
| 16 | _ = check_file; | |
| 17 | _ = progress_node; | |
| 18 | const graph = maker.graph; | |
| 19 | const arena = maker.graph.arena; // TODO don't leak into process arena | |
| 20 | const io = graph.io; | |
| 21 | const step = maker.stepByIndex(step_index); | |
| 22 | const conf = &maker.scanned_config.configuration; | |
| 23 | const conf_step = step_index.ptr(conf); | |
| 24 | const conf_cf = conf_step.extended.get(conf.extra).check_file; | |
| 25 | const lazy_path = conf_cf.file.get(conf); | |
| 26 | ||
| 27 | try step.singleUnchangingWatchInput(maker, arena, lazy_path); | |
| 28 | ||
| 29 | const src_path = try maker.resolveLazyPath(arena, lazy_path, step_index); | |
| 30 | const limit: Io.Limit = if (conf_cf.max_bytes.value) |x| .limited(x) else .unlimited; | |
| 31 | ||
| 32 | const contents = src_path.root_dir.handle.readFileAlloc(io, src_path.sub_path, arena, limit) catch |err| | |
| 33 | return step.fail(maker, "failed to read {f}: {t}", .{ src_path, err }); | |
| 34 | ||
| 35 | for (conf_cf.expected_matches.slice) |expected_match_index| { | |
| 36 | const expected_match = expected_match_index.slice(conf); | |
| 37 | if (std.mem.find(u8, contents, expected_match) == null) { | |
| 38 | return step.fail(maker, | |
| 39 | \\ | |
| 40 | \\========= expected to find: =================== | |
| 41 | \\{s} | |
| 42 | \\========= but file does not contain it: ======= | |
| 43 | \\{s} | |
| 44 | \\=============================================== | |
| 45 | , .{ expected_match, contents }); | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | if (conf_cf.expected_exact.value) |expected_exact_index| { | |
| 50 | const expected_exact = expected_exact_index.slice(conf); | |
| 51 | if (!std.mem.eql(u8, expected_exact, contents)) { | |
| 52 | return step.fail(maker, | |
| 53 | \\ | |
| 54 | \\========= expected: ===================== | |
| 55 | \\{s} | |
| 56 | \\========= but found: ==================== | |
| 57 | \\{s} | |
| 58 | \\========= from the following file: ====== | |
| 59 | \\{f} | |
| 60 | , .{ expected_exact, contents, src_path }); | |
| 61 | } | |
| 62 | } | |
| 63 | } |
lib/compiler/Maker/Step/Compile.zig created+1390| ... | ... | @@ -0,0 +1,1390 @@ |
| 1 | const Compile = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | const mem = std.mem; | |
| 6 | const Configuration = std.Build.Configuration; | |
| 7 | const Dir = std.Io.Dir; | |
| 8 | const Path = std.Build.Cache.Path; | |
| 9 | const Module = std.Build.Configuration.Module; | |
| 10 | const Io = std.Io; | |
| 11 | const Sha256 = std.crypto.hash.sha2.Sha256; | |
| 12 | const assert = std.debug.assert; | |
| 13 | const allocPrint = std.fmt.allocPrint; | |
| 14 | ||
| 15 | const Step = @import("../Step.zig"); | |
| 16 | const Maker = @import("../../Maker.zig"); | |
| 17 | const PkgConfig = @import("../PkgConfig.zig"); | |
| 18 | ||
| 19 | /// Populated when there is compiler process that lives across multiple calls | |
| 20 | /// to `make`. | |
| 21 | zig_process: ?*Step.ZigProcess = null, | |
| 22 | /// Populated by InstallArtifact. | |
| 23 | installed_path: ?Path = null, | |
| 24 | /// Populated by `make`, used by `Run`. | |
| 25 | is_linking_libc: bool = false, | |
| 26 | ||
| 27 | pub fn make( | |
| 28 | compile: *Compile, | |
| 29 | compile_index: Configuration.Step.Index, | |
| 30 | maker: *Maker, | |
| 31 | progress_node: std.Progress.Node, | |
| 32 | ) Step.ExtendedMakeError!void { | |
| 33 | const graph = maker.graph; | |
| 34 | const gpa = maker.gpa; | |
| 35 | const conf = &maker.scanned_config.configuration; | |
| 36 | const conf_step = compile_index.ptr(conf); | |
| 37 | const conf_comp = conf_step.extended.get(conf.extra).compile; | |
| 38 | ||
| 39 | var arena_allocator: std.heap.ArenaAllocator = .init(gpa); | |
| 40 | defer arena_allocator.deinit(); | |
| 41 | const arena = arena_allocator.allocator(); | |
| 42 | ||
| 43 | var argv: std.ArrayList([]const u8) = .empty; | |
| 44 | defer argv.deinit(gpa); | |
| 45 | ||
| 46 | try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, false); | |
| 47 | ||
| 48 | const maybe_output_dir = Step.evalZigProcess( | |
| 49 | compile_index, | |
| 50 | maker, | |
| 51 | argv.items, | |
| 52 | progress_node, | |
| 53 | (graph.incremental == true) and (maker.watch or maker.web_server != null), | |
| 54 | ) catch |err| switch (err) { | |
| 55 | error.NeedCompileErrorCheck => { | |
| 56 | try checkCompileErrors(arena, maker, compile_index); | |
| 57 | return; | |
| 58 | }, | |
| 59 | else => |e| return e, | |
| 60 | }; | |
| 61 | ||
| 62 | const root_module = conf_comp.root_module.get(conf); | |
| 63 | const target = root_module.resolved_target.get(conf).?.result.get(conf); | |
| 64 | ||
| 65 | // Update generated files | |
| 66 | if (maybe_output_dir) |output_dir| { | |
| 67 | if (conf_comp.emit_directory.value) |gf| maker.generatedPath(gf).* = output_dir; | |
| 68 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_bin.value, .bin); | |
| 69 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_pdb.value, .pdb); | |
| 70 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_implib.value, .implib); | |
| 71 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_h.value, .h); | |
| 72 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_docs.value, .docs); | |
| 73 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_asm.value, .@"asm"); | |
| 74 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_llvm_ir.value, .llvm_ir); | |
| 75 | try updateGeneratedFile(maker, arena, &conf_comp, output_dir, &target, conf_comp.generated_llvm_bc.value, .llvm_bc); | |
| 76 | } | |
| 77 | ||
| 78 | if (conf_comp.flags3.kind == .lib and conf_comp.flags2.linkage == .dynamic and | |
| 79 | conf_comp.version.value != null and target.flags.os_tag != .windows) | |
| 80 | { | |
| 81 | if (conf_comp.generated_bin.value) |generated_bin| { | |
| 82 | const full_dest_path = maker.generatedPath(generated_bin).*; | |
| 83 | try maker.installSymLinks(arena, full_dest_path, compile_index, compile_index); | |
| 84 | } | |
| 85 | } | |
| 86 | } | |
| 87 | ||
| 88 | fn updateGeneratedFile( | |
| 89 | maker: *Maker, | |
| 90 | arena: Allocator, | |
| 91 | conf_comp: *const Configuration.Step.Compile, | |
| 92 | out_path: std.Build.Cache.Path, | |
| 93 | target: *const Configuration.TargetQuery, | |
| 94 | opt_gf: ?Configuration.GeneratedFileIndex, | |
| 95 | ea: std.zig.EmitArtifact, | |
| 96 | ) Allocator.Error!void { | |
| 97 | const gf = opt_gf orelse return; | |
| 98 | const graph = maker.graph; | |
| 99 | const conf = &maker.scanned_config.configuration; | |
| 100 | const name = try ea.cacheName(arena, .{ | |
| 101 | .root_name = conf_comp.root_name.slice(conf), | |
| 102 | .cpu_arch = target.flags.cpu_arch.unwrap().?, | |
| 103 | .os_tag = target.flags.os_tag.unwrap().?, | |
| 104 | .ofmt = target.flags.object_format.unwrap().?, | |
| 105 | .abi = target.flags.abi.unwrap().?, | |
| 106 | .output_mode = switch (conf_comp.flags3.kind) { | |
| 107 | .lib => .Lib, | |
| 108 | .obj, .test_obj => .Obj, | |
| 109 | .exe, .@"test" => .Exe, | |
| 110 | }, | |
| 111 | .link_mode = conf_comp.flags2.linkage.unwrap(), | |
| 112 | .version = if (conf_comp.version.value) |v| | |
| 113 | std.SemanticVersion.parse(v.slice(conf)) catch unreachable | |
| 114 | else | |
| 115 | null, | |
| 116 | }); | |
| 117 | maker.generatedPath(gf).* = try out_path.join(graph.arena, name); | |
| 118 | } | |
| 119 | ||
| 120 | /// List of importable modules in a compilation's module graph, including | |
| 121 | /// the root module. The root module is guaranteed to be first. | |
| 122 | const ModuleList = std.AutoArrayHashMapUnmanaged(Configuration.Module.Index, Configuration.String); | |
| 123 | /// Keyed on the first key in the module list. | |
| 124 | pub const ModuleGraph = std.ArrayHashMapUnmanaged(ModuleList, void, ModuleListContext, false); | |
| 125 | ||
| 126 | const ModuleListContext = struct { | |
| 127 | pub fn eql(ctx: @This(), a: ModuleList, b: ModuleList) bool { | |
| 128 | _ = ctx; | |
| 129 | return a.keys()[0] == b.keys()[0]; | |
| 130 | } | |
| 131 | ||
| 132 | pub fn hash(ctx: @This(), key: ModuleList) u32 { | |
| 133 | _ = ctx; | |
| 134 | return std.hash.int(@intFromEnum(key.keys()[0])); | |
| 135 | } | |
| 136 | ||
| 137 | const Adapter = struct { | |
| 138 | pub fn eql(ctx: @This(), a: Configuration.Module.Index, b: ModuleList, b_index: usize) bool { | |
| 139 | _ = ctx; | |
| 140 | _ = b_index; | |
| 141 | return a == b.keys()[0]; | |
| 142 | } | |
| 143 | ||
| 144 | pub fn hash(ctx: @This(), key: Configuration.Module.Index) u32 { | |
| 145 | _ = ctx; | |
| 146 | return std.hash.int(@intFromEnum(key)); | |
| 147 | } | |
| 148 | }; | |
| 149 | }; | |
| 150 | ||
| 151 | fn lowerZigArgs( | |
| 152 | arena: Allocator, | |
| 153 | compile: *Compile, | |
| 154 | compile_index: Configuration.Step.Index, | |
| 155 | maker: *Maker, | |
| 156 | progress_node: std.Progress.Node, | |
| 157 | zig_args: *std.ArrayList([]const u8), | |
| 158 | fuzz: bool, | |
| 159 | ) Step.ExtendedMakeError!void { | |
| 160 | const step = maker.stepByIndex(compile_index); | |
| 161 | const graph = maker.graph; | |
| 162 | const gpa = maker.gpa; | |
| 163 | const conf = &maker.scanned_config.configuration; | |
| 164 | const conf_step = compile_index.ptr(conf); | |
| 165 | const conf_comp = conf_step.extended.get(conf.extra).compile; | |
| 166 | const root_module_target = conf_comp.rootModuleTarget(conf); | |
| 167 | ||
| 168 | try zig_args.append(gpa, graph.zig_exe); | |
| 169 | ||
| 170 | const cmd = switch (conf_comp.flags3.kind) { | |
| 171 | .lib => "build-lib", | |
| 172 | .exe => "build-exe", | |
| 173 | .obj => "build-obj", | |
| 174 | .@"test" => "test", | |
| 175 | .test_obj => "test-obj", | |
| 176 | }; | |
| 177 | try zig_args.append(gpa, cmd); | |
| 178 | ||
| 179 | if (graph.reference_trace) |some| { | |
| 180 | try zig_args.append(gpa, try allocPrint(arena, "-freference-trace={d}", .{some})); | |
| 181 | } | |
| 182 | try addFlag(gpa, zig_args, "allow-so-scripts", conf_comp.flags2.allow_so_scripts.toBool() orelse graph.allow_so_scripts); | |
| 183 | ||
| 184 | try addFlag(gpa, zig_args, "llvm", conf_comp.flags2.use_llvm.toBool()); | |
| 185 | try addFlag(gpa, zig_args, "lld", conf_comp.flags2.use_lld.toBool()); | |
| 186 | try addFlag(gpa, zig_args, "new-linker", conf_comp.flags2.use_new_linker.toBool()); | |
| 187 | ||
| 188 | const root_module = conf_comp.root_module.get(conf); | |
| 189 | ||
| 190 | if (root_module.resolved_target.get(conf).?.query.unwrap()) |query| { | |
| 191 | if (query.get(conf).flags.object_format.unwrap()) |ofmt| { | |
| 192 | try zig_args.append(gpa, try allocPrint(arena, "-ofmt={t}", .{ofmt})); | |
| 193 | } | |
| 194 | } | |
| 195 | ||
| 196 | switch (conf_comp.flags3.entry) { | |
| 197 | .default => {}, | |
| 198 | .disabled => try zig_args.append(gpa, "-fno-entry"), | |
| 199 | .enabled => try zig_args.append(gpa, "-fentry"), | |
| 200 | .symbol_name => { | |
| 201 | const symbol_name = conf_comp.entry.value.?.slice(conf); | |
| 202 | try zig_args.append(gpa, try allocPrint(arena, "-fentry={s}", .{symbol_name})); | |
| 203 | }, | |
| 204 | } | |
| 205 | ||
| 206 | for (conf_comp.force_undefined_symbols.slice) |symbol_name| { | |
| 207 | try zig_args.appendSlice(gpa, &.{ "--force_undefined", symbol_name.slice(conf) }); | |
| 208 | } | |
| 209 | ||
| 210 | if (conf_comp.stack_size.value) |stack_size| { | |
| 211 | try zig_args.appendSlice(gpa, &.{ "--stack", try allocPrint(arena, "{d}", .{stack_size}) }); | |
| 212 | } | |
| 213 | ||
| 214 | try addBool(gpa, zig_args, "-ffuzz", fuzz); | |
| 215 | ||
| 216 | { | |
| 217 | var is_linking_libc = conf_comp.flags3.is_linking_libc; | |
| 218 | var is_linking_libcpp = conf_comp.flags3.is_linking_libcpp; | |
| 219 | ||
| 220 | // Stores system libraries that have already been seen for at least one | |
| 221 | // module, along with any C compiler arguments that need to be passed | |
| 222 | // to the compiler for each module individually as reported by | |
| 223 | // pkg-config. | |
| 224 | var seen_system_libs: std.AutoArrayHashMapUnmanaged(Configuration.String, []const []const u8) = .empty; | |
| 225 | var frameworks: std.AutoArrayHashMapUnmanaged(Configuration.String, Configuration.Module.Framework.Flags) = .empty; | |
| 226 | var module_graph: ModuleGraph = .empty; | |
| 227 | ||
| 228 | var prev_has_cflags = false; | |
| 229 | var prev_has_rcflags = false; | |
| 230 | var prev_search_strategy: Configuration.SystemLib.SearchStrategy = .paths_first; | |
| 231 | var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic; | |
| 232 | // Track the number of positional arguments so that a nice error can be | |
| 233 | // emitted if there is nothing to link. | |
| 234 | var total_linker_objects: usize = @intFromBool(root_module.root_source_file != .none); | |
| 235 | ||
| 236 | // Fully recursive iteration including dynamic libraries to detect | |
| 237 | // libc and libc++ linkage. | |
| 238 | for (try getCompileDependencies(arena, &module_graph, conf, compile_index, true)) |some_compile_index| { | |
| 239 | const some_compile = some_compile_index.ptr(conf).extended.get(conf.extra).compile; | |
| 240 | const modules = try getModuleList(arena, &module_graph, some_compile.root_module, conf); | |
| 241 | for (modules.keys()) |mod_index| { | |
| 242 | const mod = mod_index.get(conf); | |
| 243 | is_linking_libc = is_linking_libc or mod.flags2.link_libc == .true; | |
| 244 | is_linking_libcpp = is_linking_libcpp or mod.flags2.link_libcpp == .true; | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | var cli_named_modules = try CliNamedModules.init(arena, &module_graph, compile_index, maker); | |
| 249 | ||
| 250 | // For this loop, don't chase dynamic libraries because their link | |
| 251 | // objects are already linked. | |
| 252 | for (try getCompileDependencies(arena, &module_graph, conf, compile_index, false)) |dep_compile_index| { | |
| 253 | const dep_compile = dep_compile_index.ptr(conf).extended.get(conf.extra).compile; | |
| 254 | const modules = try getModuleList(arena, &module_graph, dep_compile.root_module, conf); | |
| 255 | for (modules.keys()) |mod_index| { | |
| 256 | const mod = mod_index.get(conf); | |
| 257 | // While walking transitive dependencies, if a given link object is | |
| 258 | // already included in a library, it should not redundantly be | |
| 259 | // placed on the linker line of the dependee. | |
| 260 | const my_responsibility = dep_compile_index == compile_index; | |
| 261 | const already_linked = !my_responsibility and dep_compile.isDynamicLibrary(); | |
| 262 | ||
| 263 | // Inherit dependencies on darwin frameworks. | |
| 264 | if (!already_linked) { | |
| 265 | for (mod.frameworks.slice) |framework| { | |
| 266 | try frameworks.put(arena, framework.name, framework.flags); | |
| 267 | } | |
| 268 | } | |
| 269 | ||
| 270 | // Inherit dependencies on system libraries and static libraries. | |
| 271 | for (0..mod.link_objects.len) |lo_i| switch (mod.link_objects.get(conf.extra, lo_i)) { | |
| 272 | .static_path => |static_path| { | |
| 273 | if (my_responsibility) { | |
| 274 | try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, static_path, compile_index)); | |
| 275 | total_linker_objects += 1; | |
| 276 | } | |
| 277 | }, | |
| 278 | .system_lib => |system_lib_index| { | |
| 279 | const system_lib = system_lib_index.get(conf); | |
| 280 | const system_lib_name = system_lib.name.slice(conf); | |
| 281 | const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name); | |
| 282 | if (system_lib_gop.found_existing) { | |
| 283 | try zig_args.appendSlice(gpa, system_lib_gop.value_ptr.*); | |
| 284 | continue; | |
| 285 | } else { | |
| 286 | system_lib_gop.value_ptr.* = &.{}; | |
| 287 | } | |
| 288 | ||
| 289 | if (already_linked) | |
| 290 | continue; | |
| 291 | ||
| 292 | if ((system_lib.flags.search_strategy != prev_search_strategy or | |
| 293 | system_lib.flags.preferred_link_mode != prev_preferred_link_mode) and | |
| 294 | conf_comp.flags2.linkage != .static) | |
| 295 | { | |
| 296 | try zig_args.ensureUnusedCapacity(gpa, 1); | |
| 297 | switch (system_lib.flags.search_strategy) { | |
| 298 | .no_fallback => switch (system_lib.flags.preferred_link_mode) { | |
| 299 | .dynamic => zig_args.appendAssumeCapacity("-search_dylibs_only"), | |
| 300 | .static => zig_args.appendAssumeCapacity("-search_static_only"), | |
| 301 | }, | |
| 302 | .paths_first => switch (system_lib.flags.preferred_link_mode) { | |
| 303 | .dynamic => zig_args.appendAssumeCapacity("-search_paths_first"), | |
| 304 | .static => zig_args.appendAssumeCapacity("-search_paths_first_static"), | |
| 305 | }, | |
| 306 | .mode_first => switch (system_lib.flags.preferred_link_mode) { | |
| 307 | .dynamic => zig_args.appendAssumeCapacity("-search_dylibs_first"), | |
| 308 | .static => zig_args.appendAssumeCapacity("-search_static_first"), | |
| 309 | }, | |
| 310 | } | |
| 311 | prev_search_strategy = system_lib.flags.search_strategy; | |
| 312 | prev_preferred_link_mode = system_lib.flags.preferred_link_mode; | |
| 313 | } | |
| 314 | ||
| 315 | const prefix: []const u8 = prefix: { | |
| 316 | if (system_lib.flags.needed) break :prefix "-needed-l"; | |
| 317 | if (system_lib.flags.weak) break :prefix "-weak-l"; | |
| 318 | break :prefix "-l"; | |
| 319 | }; | |
| 320 | l: { | |
| 321 | pc: { | |
| 322 | const force = switch (system_lib.flags.use_pkg_config) { | |
| 323 | .no => break :pc, | |
| 324 | .yes => false, | |
| 325 | .force => true, | |
| 326 | }; | |
| 327 | ||
| 328 | const pkg_conf_node = progress_node.start("pkg-config", 0); | |
| 329 | defer pkg_conf_node.end(); | |
| 330 | ||
| 331 | if (PkgConfig.run(maker, step, pkg_conf_node, system_lib_name, force)) |result| { | |
| 332 | try zig_args.appendSlice(gpa, result.cflags); | |
| 333 | try zig_args.appendSlice(gpa, result.libs); | |
| 334 | try seen_system_libs.put(arena, system_lib.name, result.cflags); | |
| 335 | break :l; | |
| 336 | } else |err| switch (err) { | |
| 337 | error.PkgConfigUnavailable, | |
| 338 | error.PackageNotFound, | |
| 339 | => { | |
| 340 | // pkg-config failed, so fall back to linking the library by name directly. | |
| 341 | assert(!force); | |
| 342 | break :pc; | |
| 343 | }, | |
| 344 | else => |e| return e, | |
| 345 | } | |
| 346 | } | |
| 347 | try zig_args.append(gpa, try allocPrint(arena, "{s}{s}", .{ | |
| 348 | prefix, system_lib_name, | |
| 349 | })); | |
| 350 | } | |
| 351 | }, | |
| 352 | .other_step => |other_step_index| { | |
| 353 | const other = other_step_index.ptr(conf); | |
| 354 | const other_compile = other.extended.get(conf.extra).compile; | |
| 355 | switch (other_compile.flags3.kind) { | |
| 356 | .exe => return step.fail(maker, "cannot link with an executable build artifact", .{}), | |
| 357 | .@"test" => return step.fail(maker, "cannot link with a test", .{}), | |
| 358 | .obj, .test_obj => { | |
| 359 | const included_in_lib_or_obj = switch (dep_compile.flags3.kind) { | |
| 360 | .lib, .obj, .test_obj => !my_responsibility, | |
| 361 | else => false, | |
| 362 | }; | |
| 363 | if (!already_linked and !included_in_lib_or_obj) { | |
| 364 | try zig_args.append(gpa, try maker.resolveLazyPathAbs( | |
| 365 | arena, | |
| 366 | .{ .generated = .{ .index = other_compile.generated_bin.value.? } }, | |
| 367 | compile_index, | |
| 368 | )); | |
| 369 | total_linker_objects += 1; | |
| 370 | } | |
| 371 | }, | |
| 372 | .lib => l: { | |
| 373 | const other_produces_implib = other_compile.producesImplib(conf); | |
| 374 | const other_is_static = other_produces_implib or other_compile.isStaticLibrary(); | |
| 375 | ||
| 376 | if (conf_comp.isStaticLibrary() and other_is_static) { | |
| 377 | // Avoid putting a static library inside a static library. | |
| 378 | break :l; | |
| 379 | } | |
| 380 | ||
| 381 | // For DLLs, we must link against the implib. | |
| 382 | // For everything else, we directly link | |
| 383 | // against the library file. | |
| 384 | const full_path_lib = try maker.resolveLazyPathAbs( | |
| 385 | arena, | |
| 386 | .{ .generated = .{ | |
| 387 | .index = if (other_produces_implib) | |
| 388 | other_compile.generated_implib.value.? | |
| 389 | else | |
| 390 | other_compile.generated_bin.value.?, | |
| 391 | } }, | |
| 392 | compile_index, | |
| 393 | ); | |
| 394 | ||
| 395 | try zig_args.append(gpa, full_path_lib); | |
| 396 | total_linker_objects += 1; | |
| 397 | ||
| 398 | if (other_compile.flags2.linkage == .dynamic and | |
| 399 | root_module_target.flags.os_tag != .windows) | |
| 400 | { | |
| 401 | if (Dir.path.dirname(full_path_lib)) |dirname| { | |
| 402 | try zig_args.appendSlice(gpa, &.{ "-rpath", dirname }); | |
| 403 | } | |
| 404 | } | |
| 405 | }, | |
| 406 | } | |
| 407 | }, | |
| 408 | .assembly_file => |asm_file| l: { | |
| 409 | if (!my_responsibility) break :l; | |
| 410 | ||
| 411 | if (prev_has_cflags) { | |
| 412 | try zig_args.appendSlice(gpa, &.{ "-cflags", "--" }); | |
| 413 | prev_has_cflags = false; | |
| 414 | } | |
| 415 | try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, asm_file, compile_index)); | |
| 416 | total_linker_objects += 1; | |
| 417 | }, | |
| 418 | ||
| 419 | .c_source_file => |c_source_file_index| l: { | |
| 420 | if (!my_responsibility) break :l; | |
| 421 | ||
| 422 | const c_source_file = c_source_file_index.get(conf); | |
| 423 | ||
| 424 | if (prev_has_cflags or c_source_file.args.slice.len != 0) { | |
| 425 | try zig_args.ensureUnusedCapacity(gpa, 2 + c_source_file.args.slice.len); | |
| 426 | zig_args.appendAssumeCapacity("-cflags"); | |
| 427 | for (c_source_file.args.slice) |arg| { | |
| 428 | zig_args.appendAssumeCapacity(arg.slice(conf)); | |
| 429 | } | |
| 430 | zig_args.appendAssumeCapacity("--"); | |
| 431 | } | |
| 432 | prev_has_cflags = (c_source_file.args.slice.len != 0); | |
| 433 | ||
| 434 | if (c_source_file.flags.lang.get()) |lang| | |
| 435 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", lang.clangIdentifier() }; | |
| 436 | ||
| 437 | try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, c_source_file.file, compile_index)); | |
| 438 | ||
| 439 | if (c_source_file.flags.lang != .default) | |
| 440 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", "none" }; | |
| 441 | ||
| 442 | total_linker_objects += 1; | |
| 443 | }, | |
| 444 | ||
| 445 | .c_source_files => |c_source_files_index| l: { | |
| 446 | if (!my_responsibility) break :l; | |
| 447 | ||
| 448 | const c_source_files = c_source_files_index.get(conf); | |
| 449 | ||
| 450 | if (prev_has_cflags or c_source_files.args.slice.len != 0) { | |
| 451 | try zig_args.ensureUnusedCapacity(gpa, 2 + c_source_files.args.slice.len); | |
| 452 | zig_args.appendAssumeCapacity("-cflags"); | |
| 453 | for (c_source_files.args.slice) |arg| { | |
| 454 | zig_args.appendAssumeCapacity(arg.slice(conf)); | |
| 455 | } | |
| 456 | zig_args.appendAssumeCapacity("--"); | |
| 457 | } | |
| 458 | prev_has_cflags = (c_source_files.args.slice.len != 0); | |
| 459 | ||
| 460 | if (c_source_files.flags.lang.get()) |lang| | |
| 461 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", lang.clangIdentifier() }; | |
| 462 | ||
| 463 | const root_path = try maker.resolveLazyPathIndexAbs(arena, c_source_files.root, compile_index); | |
| 464 | try zig_args.ensureUnusedCapacity(gpa, c_source_files.sub_paths.slice.len); | |
| 465 | for (c_source_files.sub_paths.slice) |sub_path| { | |
| 466 | zig_args.appendAssumeCapacity(try Dir.path.join(arena, &.{ | |
| 467 | root_path, sub_path.slice(conf), | |
| 468 | })); | |
| 469 | } | |
| 470 | ||
| 471 | if (c_source_files.flags.lang != .default) | |
| 472 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-x", "none" }; | |
| 473 | ||
| 474 | total_linker_objects += c_source_files.sub_paths.slice.len; | |
| 475 | }, | |
| 476 | ||
| 477 | .win32_resource_file => |rc_source_file_index| l: { | |
| 478 | if (!my_responsibility) break :l; | |
| 479 | ||
| 480 | const rc_source_file = rc_source_file_index.get(conf); | |
| 481 | ||
| 482 | if (rc_source_file.args.slice.len == 0 and rc_source_file.include_paths.slice.len == 0) { | |
| 483 | if (prev_has_rcflags) { | |
| 484 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-rcflags", "--" }; | |
| 485 | prev_has_rcflags = false; | |
| 486 | } | |
| 487 | } else { | |
| 488 | try zig_args.ensureUnusedCapacity(gpa, 1 + rc_source_file.args.slice.len); | |
| 489 | zig_args.appendAssumeCapacity("-rcflags"); | |
| 490 | for (rc_source_file.args.slice) |arg| { | |
| 491 | zig_args.appendAssumeCapacity(arg.slice(conf)); | |
| 492 | } | |
| 493 | try zig_args.ensureUnusedCapacity(gpa, 1 + 2 * rc_source_file.include_paths.slice.len); | |
| 494 | for (rc_source_file.include_paths.slice) |include_path| { | |
| 495 | zig_args.appendAssumeCapacity("/I"); | |
| 496 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, include_path, compile_index)); | |
| 497 | } | |
| 498 | zig_args.appendAssumeCapacity("--"); | |
| 499 | prev_has_rcflags = true; | |
| 500 | } | |
| 501 | try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, rc_source_file.file, compile_index)); | |
| 502 | total_linker_objects += 1; | |
| 503 | }, | |
| 504 | }; | |
| 505 | ||
| 506 | // We need to emit the --mod argument here so that the above link objects | |
| 507 | // have the correct parent module, but only if the module is part of | |
| 508 | // this compilation. | |
| 509 | if (!my_responsibility) continue; | |
| 510 | if (cli_named_modules.modules.getIndex(mod_index)) |module_cli_index| { | |
| 511 | const module_cli_name = cli_named_modules.names.keys()[module_cli_index]; | |
| 512 | const module_index = cli_named_modules.modules.keys()[module_cli_index]; | |
| 513 | try appendModuleFlags(arena, module_index, zig_args, compile_index, maker); | |
| 514 | ||
| 515 | const imports = mod.import_table.get(conf).imports.mal; | |
| 516 | ||
| 517 | // --dep arguments | |
| 518 | try zig_args.ensureUnusedCapacity(gpa, imports.len * 2); | |
| 519 | for (imports.items(.name), imports.items(.module)) |name, import| { | |
| 520 | const import_index = cli_named_modules.modules.getIndex(import).?; | |
| 521 | const import_cli_name = cli_named_modules.names.keys()[import_index]; | |
| 522 | zig_args.appendAssumeCapacity("--dep"); | |
| 523 | const name_slice = name.slice(conf); | |
| 524 | if (mem.eql(u8, import_cli_name, name_slice)) { | |
| 525 | zig_args.appendAssumeCapacity(import_cli_name); | |
| 526 | } else { | |
| 527 | zig_args.appendAssumeCapacity(try allocPrint(arena, "{s}={s}", .{ | |
| 528 | name_slice, import_cli_name, | |
| 529 | })); | |
| 530 | } | |
| 531 | } | |
| 532 | ||
| 533 | // When the CLI sees a -M argument, it determines whether it | |
| 534 | // implies the existence of a Zig compilation unit based on | |
| 535 | // whether there is a root source file. If there is no root | |
| 536 | // source file, then this is not a zig compilation unit - it is | |
| 537 | // perhaps a set of linker objects, or C source files instead. | |
| 538 | // Linker objects are added to the CLI globally, while C source | |
| 539 | // files must have a module parent. | |
| 540 | try zig_args.ensureUnusedCapacity(gpa, 1); | |
| 541 | if (mod.root_source_file.unwrap()) |lp| { | |
| 542 | const src = try maker.resolveLazyPathIndexAbs(arena, lp, compile_index); | |
| 543 | zig_args.appendAssumeCapacity(try allocPrint(arena, "-M{s}={s}", .{ module_cli_name, src })); | |
| 544 | } else if (moduleNeedsCliArg(&mod, conf)) { | |
| 545 | zig_args.appendAssumeCapacity(try allocPrint(arena, "-M{s}", .{module_cli_name})); | |
| 546 | } | |
| 547 | } | |
| 548 | } | |
| 549 | } | |
| 550 | ||
| 551 | if (total_linker_objects == 0) { | |
| 552 | return step.fail(maker, "the linker needs one or more objects to link", .{}); | |
| 553 | } | |
| 554 | ||
| 555 | for (frameworks.keys(), frameworks.values()) |name, info| { | |
| 556 | try zig_args.ensureUnusedCapacity(gpa, 2); | |
| 557 | if (info.needed) { | |
| 558 | zig_args.appendAssumeCapacity("-needed_framework"); | |
| 559 | } else if (info.weak) { | |
| 560 | zig_args.appendAssumeCapacity("-weak_framework"); | |
| 561 | } else { | |
| 562 | zig_args.appendAssumeCapacity("-framework"); | |
| 563 | } | |
| 564 | zig_args.appendAssumeCapacity(name.slice(conf)); | |
| 565 | } | |
| 566 | ||
| 567 | try zig_args.ensureUnusedCapacity(gpa, 2); | |
| 568 | if (is_linking_libcpp) zig_args.appendAssumeCapacity("-lc++"); | |
| 569 | if (is_linking_libc) zig_args.appendAssumeCapacity("-lc"); | |
| 570 | ||
| 571 | compile.is_linking_libc = is_linking_libc; | |
| 572 | } | |
| 573 | ||
| 574 | if (conf_comp.win32_manifest.value) |manifest_file| { | |
| 575 | try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, manifest_file, compile_index)); | |
| 576 | } | |
| 577 | ||
| 578 | if (conf_comp.win32_module_definition.value) |module_file| { | |
| 579 | try zig_args.append(gpa, try maker.resolveLazyPathIndexAbs(arena, module_file, compile_index)); | |
| 580 | } | |
| 581 | ||
| 582 | if (conf_comp.image_base.value) |image_base| { | |
| 583 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 584 | "--image-base", try allocPrint(arena, "0x{x}", .{image_base}), | |
| 585 | }; | |
| 586 | } | |
| 587 | ||
| 588 | for (conf_comp.filters.slice) |filter| { | |
| 589 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--test-filter", filter.slice(conf) }; | |
| 590 | } | |
| 591 | ||
| 592 | switch (conf_comp.test_runner.u) { | |
| 593 | .default => {}, | |
| 594 | .simple, .server => |lp| (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 595 | "--test-runner", try maker.resolveLazyPathIndexAbs(arena, lp, compile_index), | |
| 596 | }, | |
| 597 | } | |
| 598 | ||
| 599 | for (graph.debug_log_scopes.items) |log_scope| { | |
| 600 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--debug-log", log_scope }; | |
| 601 | } | |
| 602 | ||
| 603 | try addBool(gpa, zig_args, "--debug-compile-errors", graph.debug_compile_errors); | |
| 604 | try addBool(gpa, zig_args, "--debug-incremental", graph.debug_incremental); | |
| 605 | try addBool(gpa, zig_args, "--verbose-air", graph.verbose_air); | |
| 606 | try addBool(gpa, zig_args, "--verbose-llvm-ir", graph.verbose_llvm_ir); | |
| 607 | try addBool(gpa, zig_args, "--verbose-link", graph.verbose_link or conf_comp.flags.verbose_link); | |
| 608 | try addBool(gpa, zig_args, "--verbose-cc", graph.verbose_cc or conf_comp.flags.verbose_cc); | |
| 609 | try addBool(gpa, zig_args, "--verbose-llvm-cpu-features", graph.verbose_llvm_cpu_features); | |
| 610 | try addBool(gpa, zig_args, "--time-report", graph.time_report); | |
| 611 | ||
| 612 | if (conf_comp.generated_bin.value == null) try zig_args.append(gpa, "-fno-emit-bin"); | |
| 613 | if (conf_comp.generated_asm.value != null) try zig_args.append(gpa, "-femit-asm"); | |
| 614 | if (conf_comp.generated_docs.value != null) try zig_args.append(gpa, "-femit-docs"); | |
| 615 | if (conf_comp.generated_implib.value != null) try zig_args.append(gpa, "-femit-implib"); | |
| 616 | if (conf_comp.generated_llvm_bc.value != null) try zig_args.append(gpa, "-femit-llvm-bc"); | |
| 617 | if (conf_comp.generated_llvm_ir.value != null) try zig_args.append(gpa, "-femit-llvm-ir"); | |
| 618 | if (conf_comp.generated_h.value != null) try zig_args.append(gpa, "-femit-h"); | |
| 619 | ||
| 620 | try addFlag(gpa, zig_args, "formatted-panics", conf_comp.flags2.formatted_panics.toBool()); | |
| 621 | ||
| 622 | switch (conf_comp.flags3.compress_debug_sections) { | |
| 623 | .none => {}, | |
| 624 | .zlib => try zig_args.append(gpa, "--compress-debug-sections=zlib"), | |
| 625 | .zstd => try zig_args.append(gpa, "--compress-debug-sections=zstd"), | |
| 626 | } | |
| 627 | ||
| 628 | try addBool(gpa, zig_args, "--eh-frame-hdr", conf_comp.flags.link_eh_frame_hdr); | |
| 629 | try addBool(gpa, zig_args, "--emit-relocs", conf_comp.flags.link_emit_relocs); | |
| 630 | try addBool(gpa, zig_args, "-ffunction-sections", conf_comp.flags.link_function_sections); | |
| 631 | try addBool(gpa, zig_args, "-fdata-sections", conf_comp.flags.link_data_sections); | |
| 632 | ||
| 633 | if (conf_comp.flags2.link_gc_sections.toBool()) |x| | |
| 634 | try zig_args.append(gpa, if (x) "--gc-sections" else "--no-gc-sections"); | |
| 635 | ||
| 636 | if (!conf_comp.flags.linker_dynamicbase) | |
| 637 | try zig_args.append(gpa, "--no-dynamicbase"); | |
| 638 | ||
| 639 | try addFlag(gpa, zig_args, "allow-shlib-undefined", conf_comp.flags2.linker_allow_shlib_undefined.toBool()); | |
| 640 | if (conf_comp.flags.link_z_notext) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "notext" }; | |
| 641 | if (!conf_comp.flags.link_z_relro) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "norelro" }; | |
| 642 | if (conf_comp.flags.link_z_lazy) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "lazy" }; | |
| 643 | if (conf_comp.link_z_common_page_size.value) |size| (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 644 | "-z", try allocPrint(arena, "common-page-size={d}", .{size}), | |
| 645 | }; | |
| 646 | if (conf_comp.link_z_max_page_size.value) |size| (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 647 | "-z", try allocPrint(arena, "max-page-size={d}", .{size}), | |
| 648 | }; | |
| 649 | if (conf_comp.flags.link_z_defs) (try zig_args.addManyAsArray(gpa, 2)).* = .{ "-z", "defs" }; | |
| 650 | ||
| 651 | try zig_args.ensureUnusedCapacity(gpa, 2); | |
| 652 | if (conf_comp.libc_file.value) |libc_file| { | |
| 653 | zig_args.appendAssumeCapacity("--libc"); | |
| 654 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, libc_file, compile_index)); | |
| 655 | } else if (graph.libc_file) |libc_file| { | |
| 656 | zig_args.appendAssumeCapacity("--libc"); | |
| 657 | zig_args.appendAssumeCapacity(libc_file); | |
| 658 | } | |
| 659 | ||
| 660 | (try zig_args.addManyAsArray(gpa, 4)).* = .{ | |
| 661 | "--cache-dir", graph.local_cache_root.path orelse ".", | |
| 662 | "--global-cache-dir", graph.global_cache_root.path orelse ".", | |
| 663 | }; | |
| 664 | ||
| 665 | try zig_args.ensureUnusedCapacity(gpa, 1); | |
| 666 | if (graph.debug_compiler_runtime_libs) |mode| switch (mode) { | |
| 667 | .Debug => zig_args.appendAssumeCapacity("--debug-rt"), | |
| 668 | else => zig_args.appendAssumeCapacity(try allocPrint(arena, "--debug-rt={t}", .{mode})), | |
| 669 | }; | |
| 670 | ||
| 671 | { | |
| 672 | try zig_args.ensureUnusedCapacity(gpa, 7); | |
| 673 | ||
| 674 | zig_args.addManyAsArrayAssumeCapacity(2).* = .{ "--name", conf_comp.root_name.slice(conf) }; | |
| 675 | ||
| 676 | switch (conf_comp.flags2.linkage) { | |
| 677 | .dynamic => zig_args.appendAssumeCapacity("-dynamic"), | |
| 678 | .static => zig_args.appendAssumeCapacity("-static"), | |
| 679 | .default => {}, | |
| 680 | } | |
| 681 | ||
| 682 | if (conf_comp.flags3.kind == .lib and conf_comp.flags2.linkage == .dynamic) { | |
| 683 | if (conf_comp.version.value) |version| zig_args.addManyAsArrayAssumeCapacity(2).* = .{ | |
| 684 | "--version", version.slice(conf), | |
| 685 | }; | |
| 686 | ||
| 687 | const os_tag = root_module_target.flags.os_tag.unwrap().?; | |
| 688 | if (os_tag.isDarwin()) { | |
| 689 | const abi = root_module_target.flags.abi.unwrap().?; | |
| 690 | zig_args.addManyAsArrayAssumeCapacity(2).* = .{ | |
| 691 | "-install_name", | |
| 692 | if (conf_comp.install_name.value) |s| s.slice(conf) else try allocPrint( | |
| 693 | arena, | |
| 694 | "@rpath/{s}{s}{s}", | |
| 695 | .{ | |
| 696 | os_tag.libPrefix(abi), | |
| 697 | conf_comp.root_name.slice(conf), | |
| 698 | os_tag.dynamicLibSuffix(), | |
| 699 | }, | |
| 700 | ), | |
| 701 | }; | |
| 702 | } | |
| 703 | } | |
| 704 | } | |
| 705 | ||
| 706 | if (conf_comp.entitlements.value) |entitlements| { | |
| 707 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 708 | "--entitlements", try maker.resolveLazyPathIndexAbs(arena, entitlements, compile_index), | |
| 709 | }; | |
| 710 | } | |
| 711 | if (conf_comp.pagezero_size.value) |pagezero_size| { | |
| 712 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 713 | "-pagezero_size", try allocPrint(arena, "{x}", .{pagezero_size}), | |
| 714 | }; | |
| 715 | } | |
| 716 | if (conf_comp.headerpad_size.value) |headerpad_size| { | |
| 717 | (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 718 | "-headerpad", try allocPrint(arena, "{x}", .{headerpad_size}), | |
| 719 | }; | |
| 720 | } | |
| 721 | try addBool(gpa, zig_args, "-headerpad_max_install_names", conf_comp.flags.headerpad_max_install_names); | |
| 722 | try addBool(gpa, zig_args, "-dead_strip_dylibs", conf_comp.flags.dead_strip_dylibs); | |
| 723 | try addBool(gpa, zig_args, "-ObjC", conf_comp.flags.force_load_objc); | |
| 724 | try addBool(gpa, zig_args, "--discard-all", conf_comp.flags.discard_local_symbols); | |
| 725 | ||
| 726 | try addFlag(gpa, zig_args, "compiler-rt", conf_comp.flags2.bundle_compiler_rt.toBool()); | |
| 727 | try addFlag(gpa, zig_args, "ubsan-rt", conf_comp.flags2.bundle_ubsan_rt.toBool()); | |
| 728 | try addFlag(gpa, zig_args, "dll-export-fns", conf_comp.flags2.dll_export_fns.toBool()); | |
| 729 | ||
| 730 | try addBool(gpa, zig_args, "-rdynamic", conf_comp.flags.rdynamic); | |
| 731 | try addBool(gpa, zig_args, "--import-memory", conf_comp.flags.import_memory); | |
| 732 | try addBool(gpa, zig_args, "--export-memory", conf_comp.flags.export_memory); | |
| 733 | try addBool(gpa, zig_args, "--import-symbols", conf_comp.flags.import_symbols); | |
| 734 | try addBool(gpa, zig_args, "--import-table", conf_comp.flags.import_table); | |
| 735 | try addBool(gpa, zig_args, "--export-table", conf_comp.flags.export_table); | |
| 736 | try addBool(gpa, zig_args, "--shared-memory", conf_comp.flags.shared_memory); | |
| 737 | ||
| 738 | { | |
| 739 | try zig_args.ensureUnusedCapacity(gpa, 4); | |
| 740 | if (conf_comp.initial_memory.value) |initial_memory| { | |
| 741 | zig_args.appendAssumeCapacity(try allocPrint(arena, "--initial-memory={d}", .{initial_memory})); | |
| 742 | } | |
| 743 | if (conf_comp.max_memory.value) |max_memory| { | |
| 744 | zig_args.appendAssumeCapacity(try allocPrint(arena, "--max-memory={d}", .{max_memory})); | |
| 745 | } | |
| 746 | if (conf_comp.global_base.value) |global_base| { | |
| 747 | zig_args.appendAssumeCapacity(try allocPrint(arena, "--global-base={d}", .{global_base})); | |
| 748 | } | |
| 749 | switch (conf_comp.flags3.wasi_exec_model) { | |
| 750 | .default => {}, | |
| 751 | .command => zig_args.appendAssumeCapacity("-mexec-model=command"), | |
| 752 | .reactor => zig_args.appendAssumeCapacity("-mexec-model=reactor"), | |
| 753 | } | |
| 754 | } | |
| 755 | ||
| 756 | if (conf_comp.linker_script.value) |linker_script| (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 757 | "--script", try maker.resolveLazyPathIndexAbs(arena, linker_script, compile_index), | |
| 758 | }; | |
| 759 | if (conf_comp.version_script.value) |version_script| (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 760 | "--version-script", try maker.resolveLazyPathIndexAbs(arena, version_script, compile_index), | |
| 761 | }; | |
| 762 | if (conf_comp.flags2.linker_allow_undefined_version.toBool()) |x| { | |
| 763 | try zig_args.append(gpa, if (x) "--undefined-version" else "--no-undefined-version"); | |
| 764 | } | |
| 765 | ||
| 766 | if (conf_comp.flags2.linker_enable_new_dtags.toBool()) |enabled| { | |
| 767 | try zig_args.append(gpa, if (enabled) "--enable-new-dtags" else "--disable-new-dtags"); | |
| 768 | } | |
| 769 | ||
| 770 | if (conf_comp.flags3.kind == .@"test" and conf_comp.exec_cmd_args.slice.len != 0) { | |
| 771 | for (conf_comp.exec_cmd_args.slice) |cmd_arg| { | |
| 772 | try zig_args.ensureUnusedCapacity(gpa, 2); | |
| 773 | if (cmd_arg.slice(conf)) |arg| { | |
| 774 | zig_args.appendAssumeCapacity("--test-cmd"); | |
| 775 | zig_args.appendAssumeCapacity(arg); | |
| 776 | } else { | |
| 777 | zig_args.appendAssumeCapacity("--test-cmd-bin"); | |
| 778 | } | |
| 779 | } | |
| 780 | } | |
| 781 | ||
| 782 | if (graph.sysroot) |sysroot| try zig_args.appendSlice(gpa, &.{ "--sysroot", sysroot }); | |
| 783 | ||
| 784 | // -I and -L arguments that appear after the last --mod argument apply to all modules. | |
| 785 | const cwd: Io.Dir = .cwd(); | |
| 786 | const io = graph.io; | |
| 787 | ||
| 788 | for (graph.search_prefixes.items) |search_prefix| { | |
| 789 | var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| { | |
| 790 | return step.fail(maker, "unable to open prefix directory '{s}': {t}", .{ search_prefix, err }); | |
| 791 | }; | |
| 792 | defer prefix_dir.close(io); | |
| 793 | ||
| 794 | // Avoid passing -L and -I flags for nonexistent directories. | |
| 795 | // This prevents a warning, that should probably be upgraded to an error in Zig's | |
| 796 | // CLI parsing code, when the linker sees an -L directory that does not exist. | |
| 797 | ||
| 798 | if (prefix_dir.access(io, "lib", .{})) |_| { | |
| 799 | try zig_args.appendSlice(gpa, &.{ | |
| 800 | "-L", try Dir.path.join(arena, &.{ search_prefix, "lib" }), | |
| 801 | }); | |
| 802 | } else |err| switch (err) { | |
| 803 | error.FileNotFound => {}, | |
| 804 | else => |e| return step.fail(maker, "unable to access '{s}/lib' directory: {t}", .{ search_prefix, e }), | |
| 805 | } | |
| 806 | ||
| 807 | if (prefix_dir.access(io, "include", .{})) |_| { | |
| 808 | try zig_args.appendSlice(gpa, &.{ | |
| 809 | "-I", try Dir.path.join(arena, &.{ search_prefix, "include" }), | |
| 810 | }); | |
| 811 | } else |err| switch (err) { | |
| 812 | error.FileNotFound => {}, | |
| 813 | else => |e| return step.fail(maker, "unable to access '{s}/include' directory: {t}", .{ search_prefix, e }), | |
| 814 | } | |
| 815 | } | |
| 816 | ||
| 817 | if (conf_comp.flags3.rc_includes != .any) (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 818 | "-rcincludes", @tagName(conf_comp.flags3.rc_includes), | |
| 819 | }; | |
| 820 | ||
| 821 | try addFlag(gpa, zig_args, "each-lib-rpath", conf_comp.flags2.each_lib_rpath.toBool()); | |
| 822 | ||
| 823 | if (conf_comp.flags3.build_id.unwrap(conf_comp.build_id.value, conf) orelse graph.build_id) |build_id| { | |
| 824 | try zig_args.append(gpa, switch (build_id) { | |
| 825 | .hexstring => |hs| try allocPrint(arena, "--build-id=0x{x}", .{hs.toSlice()}), | |
| 826 | .none, .fast, .uuid, .sha1, .md5 => try allocPrint(arena, "--build-id={t}", .{build_id}), | |
| 827 | }); | |
| 828 | } | |
| 829 | ||
| 830 | const opt_zig_lib_dir: ?[]const u8 = if (conf_comp.zig_lib_dir.value) |dir| | |
| 831 | try maker.resolveLazyPathIndexAbs(arena, dir, compile_index) | |
| 832 | else if (graph.zig_lib_directory.path) |_| | |
| 833 | try allocPrint(arena, "{f}", .{graph.zig_lib_directory}) | |
| 834 | else | |
| 835 | null; | |
| 836 | ||
| 837 | if (opt_zig_lib_dir) |zig_lib_dir| (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 838 | "--zig-lib-dir", zig_lib_dir, | |
| 839 | }; | |
| 840 | ||
| 841 | try addFlag(gpa, zig_args, "PIE", conf_comp.flags2.pie.toBool()); | |
| 842 | ||
| 843 | try zig_args.ensureUnusedCapacity(gpa, 1); | |
| 844 | switch (conf_comp.flags3.lto) { | |
| 845 | .full => zig_args.appendAssumeCapacity("-flto=full"), | |
| 846 | .thin => zig_args.appendAssumeCapacity("-flto=thin"), | |
| 847 | .none => zig_args.appendAssumeCapacity("-fno-lto"), | |
| 848 | .default => {}, | |
| 849 | } | |
| 850 | ||
| 851 | try addFlag(gpa, zig_args, "sanitize-coverage-trace-pc-guard", conf_comp.flags2.sanitize_coverage_trace_pc_guard.toBool()); | |
| 852 | ||
| 853 | switch (conf_comp.flags3.subsystem) { | |
| 854 | .default => {}, | |
| 855 | else => |t| (try zig_args.addManyAsArray(gpa, 2)).* = .{ "--subsystem", @tagName(t) }, | |
| 856 | } | |
| 857 | ||
| 858 | try addBool(gpa, zig_args, "-municode", conf_comp.flags.mingw_unicode_entry_point); | |
| 859 | ||
| 860 | if (conf_comp.error_limit.value orelse graph.error_limit) |err_limit| (try zig_args.addManyAsArray(gpa, 2)).* = .{ | |
| 861 | "--error-limit", try allocPrint(arena, "{d}", .{err_limit}), | |
| 862 | }; | |
| 863 | ||
| 864 | try addFlag(gpa, zig_args, "incremental", graph.incremental); | |
| 865 | ||
| 866 | try zig_args.append(gpa, "--listen=-"); | |
| 867 | ||
| 868 | // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux | |
| 869 | // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and | |
| 870 | // pass that to zig, e.g. via 'zig build-lib @args.rsp' | |
| 871 | // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html | |
| 872 | var args_length: usize = 0; | |
| 873 | for (zig_args.items) |arg| { | |
| 874 | args_length += arg.len + 1; // +1 to account for null terminator | |
| 875 | } | |
| 876 | if (args_length >= 30 * 1024) { | |
| 877 | const local_cache_root = graph.local_cache_root; | |
| 878 | const args_path: Path = .{ .root_dir = local_cache_root, .sub_path = "args" }; | |
| 879 | args_path.root_dir.handle.createDirPath(io, args_path.sub_path) catch |err| | |
| 880 | return step.fail(maker, "failed creating directory {f}: {t}", .{ args_path, err }); | |
| 881 | ||
| 882 | const args_to_escape = zig_args.items[2..]; | |
| 883 | var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len); | |
| 884 | arg_blk: for (args_to_escape) |arg| { | |
| 885 | for (arg, 0..) |c, arg_idx| { | |
| 886 | if (c == '\\' or c == '"') { | |
| 887 | // Slow path for arguments that need to be escaped. We'll need to allocate and copy | |
| 888 | var escaped: std.ArrayList(u8) = .empty; | |
| 889 | try escaped.ensureTotalCapacityPrecise(arena, arg.len + 1); | |
| 890 | try escaped.appendSlice(arena, arg[0..arg_idx]); | |
| 891 | for (arg[arg_idx..]) |to_escape| { | |
| 892 | if (to_escape == '\\' or to_escape == '"') try escaped.append(arena, '\\'); | |
| 893 | try escaped.append(arena, to_escape); | |
| 894 | } | |
| 895 | escaped_args.appendAssumeCapacity(escaped.items); | |
| 896 | continue :arg_blk; | |
| 897 | } | |
| 898 | } | |
| 899 | escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument | |
| 900 | } | |
| 901 | ||
| 902 | // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with | |
| 903 | // other zig build commands running in parallel. | |
| 904 | const partially_quoted = try mem.join(arena, "\" \"", escaped_args.items); | |
| 905 | const args = try mem.concat(arena, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); | |
| 906 | ||
| 907 | var args_hash: [Sha256.digest_length]u8 = undefined; | |
| 908 | Sha256.hash(args, &args_hash, .{}); | |
| 909 | var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; | |
| 910 | _ = std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}) catch unreachable; | |
| 911 | ||
| 912 | const args_file = "args" ++ Dir.path.sep_str ++ args_hex_hash; | |
| 913 | local_cache_root.handle.access(io, args_file, .{}) catch { | |
| 914 | var af = local_cache_root.handle.createFileAtomic(io, args_file, .{ | |
| 915 | .replace = false, | |
| 916 | .make_path = true, | |
| 917 | }) catch |e| return step.fail(maker, "failed creating tmp args file {f}{s}: {t}", .{ | |
| 918 | local_cache_root, args_file, e, | |
| 919 | }); | |
| 920 | defer af.deinit(io); | |
| 921 | ||
| 922 | af.file.writeStreamingAll(io, args) catch |e| { | |
| 923 | return step.fail(maker, "failed writing args data to tmp file {f}{s}: {t}", .{ | |
| 924 | local_cache_root, args_file, e, | |
| 925 | }); | |
| 926 | }; | |
| 927 | // Note we can't clean up this file, not even after build | |
| 928 | // success, because that might interfere with another build | |
| 929 | // process that needs the same file. | |
| 930 | af.link(io) catch |e| switch (e) { | |
| 931 | error.PathAlreadyExists => { | |
| 932 | // The args file was created by another concurrent build process. | |
| 933 | }, | |
| 934 | else => |other_err| return step.fail(maker, "failed linking tmp file {f}{s}: {t}", .{ | |
| 935 | local_cache_root, args_file, other_err, | |
| 936 | }), | |
| 937 | }; | |
| 938 | }; | |
| 939 | ||
| 940 | const resolved_args_file = try mem.concat(arena, u8, &.{ | |
| 941 | "@", try local_cache_root.join(arena, &.{args_file}), | |
| 942 | }); | |
| 943 | ||
| 944 | zig_args.shrinkRetainingCapacity(2); | |
| 945 | try zig_args.append(gpa, resolved_args_file); | |
| 946 | } | |
| 947 | } | |
| 948 | ||
| 949 | pub fn rebuildInFuzzMode( | |
| 950 | compile: *Compile, | |
| 951 | maker: *Maker, | |
| 952 | compile_index: Configuration.Step.Index, | |
| 953 | progress_node: std.Progress.Node, | |
| 954 | ) !Path { | |
| 955 | const gpa = maker.gpa; | |
| 956 | const step = maker.stepByIndex(compile_index); | |
| 957 | ||
| 958 | var arena_allocator: std.heap.ArenaAllocator = .init(gpa); | |
| 959 | defer arena_allocator.deinit(); | |
| 960 | const arena = arena_allocator.allocator(); | |
| 961 | ||
| 962 | step.result_error_msgs.clearRetainingCapacity(); | |
| 963 | step.result_stderr = ""; | |
| 964 | ||
| 965 | step.result_error_bundle.deinit(gpa); | |
| 966 | step.result_error_bundle = std.zig.ErrorBundle.empty; | |
| 967 | ||
| 968 | step.clearFailedCommand(gpa); | |
| 969 | ||
| 970 | var argv: std.ArrayList([]const u8) = .empty; | |
| 971 | defer argv.deinit(gpa); | |
| 972 | ||
| 973 | try lowerZigArgs(arena, compile, compile_index, maker, progress_node, &argv, true); | |
| 974 | const maybe_output_bin_path = try Step.evalZigProcess(compile_index, maker, argv.items, progress_node, false); | |
| 975 | return maybe_output_bin_path.?; | |
| 976 | } | |
| 977 | ||
| 978 | fn addBool(gpa: Allocator, args: *std.ArrayList([]const u8), arg: []const u8, opt: bool) !void { | |
| 979 | if (opt) try args.append(gpa, arg); | |
| 980 | } | |
| 981 | ||
| 982 | fn addFlag(gpa: Allocator, args: *std.ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void { | |
| 983 | const cond = opt orelse return; | |
| 984 | try args.append(gpa, if (cond) "-f" ++ name else "-fno-" ++ name); | |
| 985 | } | |
| 986 | ||
| 987 | fn checkCompileErrors(arena: Allocator, maker: *Maker, step_index: Configuration.Step.Index) Step.ExtendedMakeError!void { | |
| 988 | const step = maker.stepByIndex(step_index); | |
| 989 | const conf = &maker.scanned_config.configuration; | |
| 990 | const conf_step = step_index.ptr(conf); | |
| 991 | const conf_comp = conf_step.extended.get(conf.extra).compile; | |
| 992 | ||
| 993 | // Clear this field so that it does not get printed by the build runner. | |
| 994 | var actual_eb = step.result_error_bundle; | |
| 995 | step.result_error_bundle = .empty; | |
| 996 | defer actual_eb.deinit(maker.gpa); | |
| 997 | ||
| 998 | const actual_errors = ae: { | |
| 999 | var aw: std.Io.Writer.Allocating = .init(arena); | |
| 1000 | defer aw.deinit(); | |
| 1001 | actual_eb.renderToWriter(.{ | |
| 1002 | .include_reference_trace = false, | |
| 1003 | .include_source_line = false, | |
| 1004 | }, &aw.writer) catch |err| switch (err) { | |
| 1005 | error.WriteFailed => return error.OutOfMemory, | |
| 1006 | }; | |
| 1007 | break :ae try aw.toOwnedSlice(); | |
| 1008 | }; | |
| 1009 | ||
| 1010 | // Render the expected lines into a string that we can compare verbatim. | |
| 1011 | var expected_generated: std.ArrayList(u8) = .empty; | |
| 1012 | var actual_line_it = mem.splitScalar(u8, actual_errors, '\n'); | |
| 1013 | ||
| 1014 | switch (conf_comp.expect_errors.u) { | |
| 1015 | .none => unreachable, | |
| 1016 | .starts_with => |expect_starts_with_string| { | |
| 1017 | const expect_starts_with = expect_starts_with_string.slice(conf); | |
| 1018 | if (mem.startsWith(u8, actual_errors, expect_starts_with)) return; | |
| 1019 | return step.fail(maker, | |
| 1020 | \\ | |
| 1021 | \\========= should start with: ============ | |
| 1022 | \\{s} | |
| 1023 | \\========= but not found: ================ | |
| 1024 | \\{s} | |
| 1025 | \\========================================= | |
| 1026 | , .{ expect_starts_with, actual_errors }); | |
| 1027 | }, | |
| 1028 | .contains => |expect_line_string| { | |
| 1029 | const expect_line = expect_line_string.slice(conf); | |
| 1030 | while (actual_line_it.next()) |actual_line| { | |
| 1031 | if (!matchCompileError(actual_line, expect_line)) continue; | |
| 1032 | return; | |
| 1033 | } | |
| 1034 | ||
| 1035 | return step.fail(maker, | |
| 1036 | \\ | |
| 1037 | \\========= should contain: =============== | |
| 1038 | \\{s} | |
| 1039 | \\========= but not found: ================ | |
| 1040 | \\{s} | |
| 1041 | \\========================================= | |
| 1042 | , .{ expect_line, actual_errors }); | |
| 1043 | }, | |
| 1044 | .stderr_contains => |expect_line_string| { | |
| 1045 | const expect_line = expect_line_string.slice(conf); | |
| 1046 | const actual_stderr: []const u8 = if (step.result_error_msgs.items.len > 0) | |
| 1047 | step.result_error_msgs.items[0] | |
| 1048 | else | |
| 1049 | &.{}; | |
| 1050 | step.result_error_msgs.clearRetainingCapacity(); | |
| 1051 | ||
| 1052 | var stderr_line_it = mem.splitScalar(u8, actual_stderr, '\n'); | |
| 1053 | ||
| 1054 | while (stderr_line_it.next()) |actual_line| { | |
| 1055 | if (!matchCompileError(actual_line, expect_line)) continue; | |
| 1056 | return; | |
| 1057 | } | |
| 1058 | ||
| 1059 | return step.fail(maker, | |
| 1060 | \\ | |
| 1061 | \\========= should contain: =============== | |
| 1062 | \\{s} | |
| 1063 | \\========= but not found: ================ | |
| 1064 | \\{s} | |
| 1065 | \\========================================= | |
| 1066 | , .{ expect_line, actual_stderr }); | |
| 1067 | }, | |
| 1068 | .exact => |expect_lines| { | |
| 1069 | for (expect_lines.slice) |expect_line_string| { | |
| 1070 | const expect_line = expect_line_string.slice(conf); | |
| 1071 | const actual_line = actual_line_it.next() orelse { | |
| 1072 | try expected_generated.appendSlice(arena, expect_line); | |
| 1073 | try expected_generated.append(arena, '\n'); | |
| 1074 | continue; | |
| 1075 | }; | |
| 1076 | if (matchCompileError(actual_line, expect_line)) { | |
| 1077 | try expected_generated.appendSlice(arena, actual_line); | |
| 1078 | try expected_generated.append(arena, '\n'); | |
| 1079 | continue; | |
| 1080 | } | |
| 1081 | try expected_generated.appendSlice(arena, expect_line); | |
| 1082 | try expected_generated.append(arena, '\n'); | |
| 1083 | } | |
| 1084 | ||
| 1085 | if (mem.eql(u8, expected_generated.items, actual_errors)) return; | |
| 1086 | ||
| 1087 | return step.fail(maker, | |
| 1088 | \\ | |
| 1089 | \\========= expected: ===================== | |
| 1090 | \\{s} | |
| 1091 | \\========= but found: ==================== | |
| 1092 | \\{s} | |
| 1093 | \\========================================= | |
| 1094 | , .{ expected_generated.items, actual_errors }); | |
| 1095 | }, | |
| 1096 | } | |
| 1097 | } | |
| 1098 | ||
| 1099 | fn matchCompileError(actual: []const u8, expected: []const u8) bool { | |
| 1100 | if (mem.endsWith(u8, actual, expected)) return true; | |
| 1101 | if (mem.startsWith(u8, expected, ":?:?: ")) { | |
| 1102 | if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true; | |
| 1103 | } | |
| 1104 | // We scan for /?/ in expected line and if there is a match, we match everything | |
| 1105 | // up to and after /?/. | |
| 1106 | const expected_trim = mem.trim(u8, expected, " "); | |
| 1107 | if (mem.find(u8, expected_trim, "/?/")) |index| { | |
| 1108 | const actual_trim = mem.trim(u8, actual, " "); | |
| 1109 | const lhs = expected_trim[0..index]; | |
| 1110 | const rhs = expected_trim[index + "/?/".len ..]; | |
| 1111 | if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true; | |
| 1112 | } | |
| 1113 | return false; | |
| 1114 | } | |
| 1115 | ||
| 1116 | fn moduleNeedsCliArg(mod: *const Configuration.Module, conf: *const Configuration) bool { | |
| 1117 | return for (0..mod.link_objects.len) |i| switch (mod.link_objects.tag(conf.extra, i)) { | |
| 1118 | .c_source_file, .c_source_files, .assembly_file, .win32_resource_file => break true, | |
| 1119 | else => continue, | |
| 1120 | } else false; | |
| 1121 | } | |
| 1122 | ||
| 1123 | const CliNamedModules = struct { | |
| 1124 | modules: std.AutoArrayHashMapUnmanaged(Configuration.Module.Index, void), | |
| 1125 | names: std.StringArrayHashMapUnmanaged(void), | |
| 1126 | ||
| 1127 | /// Traverse the whole dependency graph and give every module a unique | |
| 1128 | /// name, ideally one named after what it's called somewhere in the graph. | |
| 1129 | /// It will help here to have both a mapping from module to name and a set | |
| 1130 | /// of all the currently-used names. | |
| 1131 | fn init( | |
| 1132 | arena: Allocator, | |
| 1133 | module_graph: *ModuleGraph, | |
| 1134 | compile_index: Configuration.Step.Index, | |
| 1135 | maker: *const Maker, | |
| 1136 | ) Allocator.Error!CliNamedModules { | |
| 1137 | const conf = &maker.scanned_config.configuration; | |
| 1138 | const conf_compile = compile_index.ptr(conf).extended.get(conf.extra).compile; | |
| 1139 | ||
| 1140 | var result: CliNamedModules = .{ | |
| 1141 | .modules = .{}, | |
| 1142 | .names = .{}, | |
| 1143 | }; | |
| 1144 | const modules = try getModuleList(arena, module_graph, conf_compile.root_module, conf); | |
| 1145 | { | |
| 1146 | assert(conf_compile.root_module == modules.keys()[0]); | |
| 1147 | try result.modules.put(arena, conf_compile.root_module, {}); | |
| 1148 | try result.names.put(arena, "root", {}); | |
| 1149 | } | |
| 1150 | for (modules.keys()[1..], modules.values()[1..]) |mod, orig_name| { | |
| 1151 | const orig_name_slice = orig_name.slice(conf); | |
| 1152 | var name: []const u8 = orig_name_slice; | |
| 1153 | var n: usize = 0; | |
| 1154 | while (true) { | |
| 1155 | const gop = try result.names.getOrPut(arena, name); | |
| 1156 | if (!gop.found_existing) { | |
| 1157 | try result.modules.putNoClobber(arena, mod, {}); | |
| 1158 | break; | |
| 1159 | } | |
| 1160 | name = try allocPrint(arena, "{s}{d}", .{ orig_name_slice, n }); | |
| 1161 | n += 1; | |
| 1162 | } | |
| 1163 | } | |
| 1164 | return result; | |
| 1165 | } | |
| 1166 | }; | |
| 1167 | ||
| 1168 | pub fn getCompileDependencies( | |
| 1169 | arena: Allocator, | |
| 1170 | module_graph: *ModuleGraph, | |
| 1171 | conf: *const Configuration, | |
| 1172 | start: Configuration.Step.Index, | |
| 1173 | chase_dynamic: bool, | |
| 1174 | ) ![]const Configuration.Step.Index { | |
| 1175 | var compiles: std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, void) = .empty; | |
| 1176 | var compiles_i: usize = 0; | |
| 1177 | ||
| 1178 | try compiles.putNoClobber(arena, start, {}); | |
| 1179 | ||
| 1180 | while (compiles_i < compiles.count()) : (compiles_i += 1) { | |
| 1181 | const step = compiles.keys()[compiles_i].ptr(conf); | |
| 1182 | const compile = step.extended.get(conf.extra).compile; | |
| 1183 | const modules = try getModuleList(arena, module_graph, compile.root_module, conf); | |
| 1184 | ||
| 1185 | for (modules.keys()) |mod_index| { | |
| 1186 | const mod = mod_index.get(conf); | |
| 1187 | for (0..mod.link_objects.len) |i| { | |
| 1188 | switch (mod.link_objects.get(conf.extra, i)) { | |
| 1189 | .other_step => |other_compile_index| { | |
| 1190 | const other_compile = other_compile_index.ptr(conf).extended.get(conf.extra).compile; | |
| 1191 | if (!chase_dynamic and other_compile.isDynamicLibrary()) continue; | |
| 1192 | try compiles.put(arena, other_compile_index, {}); | |
| 1193 | }, | |
| 1194 | else => {}, | |
| 1195 | } | |
| 1196 | } | |
| 1197 | } | |
| 1198 | } | |
| 1199 | ||
| 1200 | return compiles.keys(); | |
| 1201 | } | |
| 1202 | ||
| 1203 | /// Returned pointer expires upon next call to `getModuleList`. | |
| 1204 | fn getModuleList( | |
| 1205 | arena: Allocator, | |
| 1206 | module_graph: *ModuleGraph, | |
| 1207 | root_module: Configuration.Module.Index, | |
| 1208 | conf: *const Configuration, | |
| 1209 | ) !*ModuleList { | |
| 1210 | const gop = try module_graph.getOrPutAdapted(arena, root_module, @as(ModuleListContext.Adapter, .{})); | |
| 1211 | const modules = gop.key_ptr; | |
| 1212 | ||
| 1213 | if (gop.found_existing) return modules; | |
| 1214 | modules.* = .empty; | |
| 1215 | try modules.putNoClobber(arena, root_module, .root); | |
| 1216 | ||
| 1217 | var i: usize = 0; | |
| 1218 | ||
| 1219 | while (i < modules.entries.len) : (i += 1) { | |
| 1220 | const dep_index = modules.keys()[i]; | |
| 1221 | const dep = dep_index.get(conf); | |
| 1222 | const imports = dep.import_table.get(conf).imports; | |
| 1223 | try modules.ensureUnusedCapacity(arena, imports.mal.len); | |
| 1224 | for (imports.mal.items(.name), imports.mal.items(.module)) |import_name, other_mod| | |
| 1225 | modules.putAssumeCapacity(other_mod, import_name); | |
| 1226 | } | |
| 1227 | ||
| 1228 | return modules; | |
| 1229 | } | |
| 1230 | ||
| 1231 | fn appendModuleFlags( | |
| 1232 | arena: Allocator, | |
| 1233 | module_index: Configuration.Module.Index, | |
| 1234 | zig_args: *std.ArrayList([]const u8), | |
| 1235 | asking_step: Configuration.Step.Index, | |
| 1236 | maker: *const Maker, | |
| 1237 | ) !void { | |
| 1238 | const gpa = maker.gpa; | |
| 1239 | const conf = &maker.scanned_config.configuration; | |
| 1240 | const m = module_index.get(conf); | |
| 1241 | ||
| 1242 | try addFlag(gpa, zig_args, "strip", m.flags.strip.toBool()); | |
| 1243 | try addFlag(gpa, zig_args, "single-threaded", m.flags.single_threaded.toBool()); | |
| 1244 | try addFlag(gpa, zig_args, "stack-check", m.flags.stack_check.toBool()); | |
| 1245 | try addFlag(gpa, zig_args, "stack-protector", m.flags.stack_protector.toBool()); | |
| 1246 | try addFlag(gpa, zig_args, "omit-frame-pointer", m.flags2.omit_frame_pointer.toBool()); | |
| 1247 | try addFlag(gpa, zig_args, "error-tracing", m.flags2.error_tracing.toBool()); | |
| 1248 | try addFlag(gpa, zig_args, "sanitize-thread", m.flags.sanitize_thread.toBool()); | |
| 1249 | try addFlag(gpa, zig_args, "fuzz", m.flags.fuzz.toBool()); | |
| 1250 | try addFlag(gpa, zig_args, "valgrind", m.flags2.valgrind.toBool()); | |
| 1251 | try addFlag(gpa, zig_args, "PIC", m.flags2.pic.toBool()); | |
| 1252 | try addFlag(gpa, zig_args, "red-zone", m.flags2.red_zone.toBool()); | |
| 1253 | try addFlag(gpa, zig_args, "no-builtin", m.flags2.no_builtin.toBool()); | |
| 1254 | ||
| 1255 | { | |
| 1256 | try zig_args.ensureUnusedCapacity(gpa, 6); | |
| 1257 | ||
| 1258 | switch (m.flags.sanitize_c) { | |
| 1259 | .off => zig_args.appendAssumeCapacity("-fno-sanitize-c"), | |
| 1260 | .trap => zig_args.appendAssumeCapacity("-fsanitize-c=trap"), | |
| 1261 | .full => zig_args.appendAssumeCapacity("-fsanitize-c=full"), | |
| 1262 | .default => {}, | |
| 1263 | } | |
| 1264 | ||
| 1265 | switch (m.flags.dwarf_format) { | |
| 1266 | .@"32" => zig_args.appendAssumeCapacity("-gdwarf32"), | |
| 1267 | .@"64" => zig_args.appendAssumeCapacity("-gdwarf64"), | |
| 1268 | .default => {}, | |
| 1269 | } | |
| 1270 | ||
| 1271 | switch (m.flags.unwind_tables) { | |
| 1272 | .none => zig_args.appendAssumeCapacity("-fno-unwind-tables"), | |
| 1273 | .sync => zig_args.appendAssumeCapacity("-funwind-tables"), | |
| 1274 | .async => zig_args.appendAssumeCapacity("-fasync-unwind-tables"), | |
| 1275 | .default => {}, | |
| 1276 | } | |
| 1277 | ||
| 1278 | switch (m.flags.optimize) { | |
| 1279 | .debug => zig_args.appendAssumeCapacity("-ODebug"), | |
| 1280 | .safe => zig_args.appendAssumeCapacity("-OReleaseSafe"), | |
| 1281 | .fast => zig_args.appendAssumeCapacity("-OReleaseFast"), | |
| 1282 | .small => zig_args.appendAssumeCapacity("-OReleaseSmall"), | |
| 1283 | .default => {}, | |
| 1284 | } | |
| 1285 | ||
| 1286 | if (m.flags.code_model != .default) { | |
| 1287 | zig_args.appendAssumeCapacity("-mcmodel"); | |
| 1288 | zig_args.appendAssumeCapacity(@tagName(m.flags.code_model)); | |
| 1289 | } | |
| 1290 | } | |
| 1291 | ||
| 1292 | if (m.resolved_target.get(conf)) |resolved_target| { | |
| 1293 | // Communicate the query via CLI since it's more compact. | |
| 1294 | if (resolved_target.query.get(conf)) |compact_query| { | |
| 1295 | try zig_args.ensureUnusedCapacity(gpa, 6); | |
| 1296 | ||
| 1297 | const query = compact_query.unwrap(conf); | |
| 1298 | ||
| 1299 | zig_args.appendAssumeCapacity("-target"); | |
| 1300 | zig_args.appendAssumeCapacity(try query.zigTriple(arena)); | |
| 1301 | ||
| 1302 | zig_args.appendAssumeCapacity("-mcpu"); | |
| 1303 | zig_args.appendAssumeCapacity(try query.serializeCpuAlloc(arena)); | |
| 1304 | ||
| 1305 | if (query.dynamic_linker) |*dynamic_linker| { | |
| 1306 | if (dynamic_linker.get()) |dynamic_linker_path| { | |
| 1307 | zig_args.appendAssumeCapacity("--dynamic-linker"); | |
| 1308 | zig_args.appendAssumeCapacity(dynamic_linker_path); | |
| 1309 | } else { | |
| 1310 | zig_args.appendAssumeCapacity("--no-dynamic-linker"); | |
| 1311 | } | |
| 1312 | } | |
| 1313 | } | |
| 1314 | } | |
| 1315 | ||
| 1316 | for (m.export_symbol_names.slice) |symbol_name| { | |
| 1317 | try zig_args.append(gpa, try allocPrint(arena, "--export={s}", .{symbol_name.slice(conf)})); | |
| 1318 | } | |
| 1319 | ||
| 1320 | try zig_args.ensureUnusedCapacity(gpa, 2 * m.include_dirs.len); | |
| 1321 | for (0..m.include_dirs.len) |i| | |
| 1322 | try appendIncludeDirFlags(arena, m.include_dirs.get(conf.extra, i), zig_args, asking_step, maker); | |
| 1323 | ||
| 1324 | try zig_args.ensureUnusedCapacity(gpa, m.c_macros.slice.len); | |
| 1325 | for (m.c_macros.slice) |c_macro| | |
| 1326 | zig_args.appendAssumeCapacity(c_macro.slice(conf)); | |
| 1327 | ||
| 1328 | try zig_args.ensureUnusedCapacity(gpa, 2 * m.lib_paths.slice.len); | |
| 1329 | for (m.lib_paths.slice) |lib_path| { | |
| 1330 | zig_args.appendAssumeCapacity("-L"); | |
| 1331 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lib_path, asking_step)); | |
| 1332 | } | |
| 1333 | ||
| 1334 | try zig_args.ensureUnusedCapacity(gpa, 2 * m.rpaths.len); | |
| 1335 | for (0..m.rpaths.len) |i| switch (m.rpaths.get(conf.extra, i)) { | |
| 1336 | .lazy_path => |lp| { | |
| 1337 | zig_args.appendAssumeCapacity("-rpath"); | |
| 1338 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step)); | |
| 1339 | }, | |
| 1340 | .special => |string| { | |
| 1341 | zig_args.appendAssumeCapacity("-rpath"); | |
| 1342 | zig_args.appendAssumeCapacity(string.slice(conf)); | |
| 1343 | }, | |
| 1344 | }; | |
| 1345 | } | |
| 1346 | ||
| 1347 | /// Assumes unused capacity for at least 2 items. | |
| 1348 | pub fn appendIncludeDirFlags( | |
| 1349 | arena: Allocator, | |
| 1350 | include_dir: Configuration.Module.IncludeDir, | |
| 1351 | zig_args: *std.ArrayList([]const u8), | |
| 1352 | asking_step: Configuration.Step.Index, | |
| 1353 | maker: *const Maker, | |
| 1354 | ) !void { | |
| 1355 | const conf = &maker.scanned_config.configuration; | |
| 1356 | ||
| 1357 | switch (include_dir) { | |
| 1358 | .path => |lp| { | |
| 1359 | zig_args.appendAssumeCapacity("-I"); | |
| 1360 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step)); | |
| 1361 | }, | |
| 1362 | .path_system => |lp| { | |
| 1363 | zig_args.appendAssumeCapacity("-isystem"); | |
| 1364 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step)); | |
| 1365 | }, | |
| 1366 | .path_after => |lp| { | |
| 1367 | zig_args.appendAssumeCapacity("-idirafter"); | |
| 1368 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step)); | |
| 1369 | }, | |
| 1370 | .framework_path => |lp| { | |
| 1371 | zig_args.appendAssumeCapacity("-F"); | |
| 1372 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step)); | |
| 1373 | }, | |
| 1374 | .framework_path_system => |lp| { | |
| 1375 | zig_args.appendAssumeCapacity("-iframework"); | |
| 1376 | zig_args.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, asking_step)); | |
| 1377 | }, | |
| 1378 | .config_header_step => |ch_index| { | |
| 1379 | const conf_ch = ch_index.ptr(conf).extended.get(conf.extra).config_header; | |
| 1380 | const path = maker.generatedPath(conf_ch.generated_dir).*; | |
| 1381 | zig_args.appendAssumeCapacity("-I"); | |
| 1382 | zig_args.appendAssumeCapacity(try path.toString(arena)); | |
| 1383 | }, | |
| 1384 | .embed_path => |lazy_path| { | |
| 1385 | zig_args.appendAssumeCapacity(try allocPrint(arena, "--embed-dir={f}", .{ | |
| 1386 | try maker.resolveLazyPathIndex(arena, lazy_path, asking_step), | |
| 1387 | })); | |
| 1388 | }, | |
| 1389 | } | |
| 1390 | } |
lib/compiler/Maker/Step/ConfigHeader.zig created+610| ... | ... | @@ -0,0 +1,610 @@ |
| 1 | const ConfigHeader = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Configuration = std.Build.Configuration; | |
| 6 | const Writer = std.Io.Writer; | |
| 7 | const Path = std.Build.Cache.Path; | |
| 8 | const Allocator = std.mem.Allocator; | |
| 9 | ||
| 10 | const Step = @import("../Step.zig"); | |
| 11 | const Maker = @import("../../Maker.zig"); | |
| 12 | ||
| 13 | const header_text = "This file was generated by ConfigHeader using the Zig Build System."; | |
| 14 | const c_generated_line = "/* " ++ header_text ++ " */\n"; | |
| 15 | const asm_generated_line = "; " ++ header_text ++ "\n"; | |
| 16 | ||
| 17 | /// Table value is whether the value is used. | |
| 18 | const ValueMap = std.array_hash_map.String(bool); | |
| 19 | const Value = Configuration.Step.ConfigHeader.Value; | |
| 20 | ||
| 21 | pub fn make( | |
| 22 | config_header: *ConfigHeader, | |
| 23 | step_index: Configuration.Step.Index, | |
| 24 | maker: *Maker, | |
| 25 | progress_node: std.Progress.Node, | |
| 26 | ) Step.ExtendedMakeError!void { | |
| 27 | _ = config_header; | |
| 28 | _ = progress_node; | |
| 29 | const graph = maker.graph; | |
| 30 | const step = maker.stepByIndex(step_index); | |
| 31 | const io = graph.io; | |
| 32 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 33 | const conf = &maker.scanned_config.configuration; | |
| 34 | const conf_step = step_index.ptr(conf); | |
| 35 | const conf_ch = conf_step.extended.get(conf.extra).config_header; | |
| 36 | const cache_root = graph.local_cache_root; | |
| 37 | ||
| 38 | const input_size_limit: Io.Limit = if (conf_ch.input_size_limit.value) |x| .limited64(x) else .unlimited; | |
| 39 | const include_guard_override: ?[]const u8 = if (conf_ch.include_guard.value) |s| s.slice(conf) else null; | |
| 40 | const include_path: []const u8 = conf_ch.include_path.slice(conf); | |
| 41 | const template_file = if (conf_ch.template_file.value) |lp| | |
| 42 | try maker.resolveLazyPathIndex(arena, lp, step_index) | |
| 43 | else | |
| 44 | null; | |
| 45 | const value_pairs = conf_ch.values.slice; | |
| 46 | ||
| 47 | if (conf_ch.template_file.value) |lp| try step.singleUnchangingWatchInput(maker, arena, lp.get(conf)); | |
| 48 | ||
| 49 | var value_map: ValueMap = .empty; | |
| 50 | try value_map.ensureTotalCapacity(arena, value_pairs.len); | |
| 51 | for (value_pairs) |pair| value_map.putAssumeCapacityNoClobber(pair.key.slice(conf), false); | |
| 52 | ||
| 53 | var man = graph.cache.obtain(); | |
| 54 | defer man.deinit(); | |
| 55 | ||
| 56 | // Random bytes to make ConfigHeader unique. Refresh this with new | |
| 57 | // random bytes when ConfigHeader implementation is modified in a | |
| 58 | // non-backwards-compatible way. | |
| 59 | man.hash.add(@as(u32, 0xdef08d23)); | |
| 60 | man.hash.add(@as(u32, @bitCast(conf_ch.flags))); | |
| 61 | man.hash.addBytes(include_path); | |
| 62 | man.hash.addOptionalBytes(include_guard_override); | |
| 63 | ||
| 64 | var aw: Writer.Allocating = .init(arena); | |
| 65 | defer aw.deinit(); | |
| 66 | ||
| 67 | switch (conf_ch.flags.style) { | |
| 68 | .autoconf_undef => { | |
| 69 | const tf = template_file.?; | |
| 70 | const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err| | |
| 71 | return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err }); | |
| 72 | renderAutoConfUndef(maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) { | |
| 73 | error.WriteFailed => return error.OutOfMemory, | |
| 74 | else => |e| return e, | |
| 75 | }; | |
| 76 | }, | |
| 77 | .autoconf_at => { | |
| 78 | const tf = template_file.?; | |
| 79 | const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err| | |
| 80 | return step.fail(maker, "unable to read autoconf input file {f}: {t}", .{ tf, err }); | |
| 81 | renderAutoconfAt(maker, step, contents, &aw, value_pairs, &value_map, tf) catch |err| switch (err) { | |
| 82 | error.WriteFailed => return error.OutOfMemory, | |
| 83 | else => |e| return e, | |
| 84 | }; | |
| 85 | }, | |
| 86 | .cmake => { | |
| 87 | const tf = template_file.?; | |
| 88 | const contents = tf.root_dir.handle.readFileAlloc(io, tf.sub_path, arena, input_size_limit) catch |err| | |
| 89 | return step.fail(maker, "unable to read cmake input file {f}: {t}", .{ tf, err }); | |
| 90 | renderCmake(arena, maker, step, contents, &aw.writer, value_pairs, &value_map, tf) catch |err| switch (err) { | |
| 91 | error.WriteFailed => return error.OutOfMemory, | |
| 92 | else => |e| return e, | |
| 93 | }; | |
| 94 | }, | |
| 95 | .blank => { | |
| 96 | renderBlank(conf, &aw.writer, value_pairs, &value_map, include_path, include_guard_override) catch |err| switch (err) { | |
| 97 | error.WriteFailed => return error.OutOfMemory, | |
| 98 | else => |e| return e, | |
| 99 | }; | |
| 100 | }, | |
| 101 | .nasm => { | |
| 102 | renderNasm(conf, &aw.writer, value_pairs, &value_map) catch |err| switch (err) { | |
| 103 | error.WriteFailed => return error.OutOfMemory, | |
| 104 | else => |e| return e, | |
| 105 | }; | |
| 106 | }, | |
| 107 | } | |
| 108 | ||
| 109 | const output = aw.written(); | |
| 110 | man.hash.addBytes(output); | |
| 111 | ||
| 112 | if (try step.cacheHit(maker, &man)) { | |
| 113 | const digest = man.final(); | |
| 114 | maker.generatedPath(conf_ch.generated_dir).* = .{ | |
| 115 | .root_dir = cache_root, | |
| 116 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), | |
| 117 | }; | |
| 118 | return; | |
| 119 | } | |
| 120 | ||
| 121 | const digest = man.final(); | |
| 122 | ||
| 123 | // If output_path has directory parts, deal with them. Example: | |
| 124 | // output_dir is zig-cache/o/HASH | |
| 125 | // output_path is libavutil/avconfig.h | |
| 126 | // We want to open directory zig-cache/o/HASH/libavutil/ | |
| 127 | // but keep output_dir as zig-cache/o/HASH for -I include | |
| 128 | const out_path: Path = .{ | |
| 129 | .root_dir = cache_root, | |
| 130 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, conf_ch.include_path.slice(conf) }), | |
| 131 | }; | |
| 132 | const out_path_dirname = out_path.dirname().?; | |
| 133 | ||
| 134 | out_path_dirname.root_dir.handle.createDirPath(io, out_path_dirname.sub_path) catch |err| | |
| 135 | return step.fail(maker, "unable to make path {f}: {t}", .{ out_path_dirname, err }); | |
| 136 | ||
| 137 | out_path.root_dir.handle.writeFile(io, .{ .sub_path = out_path.sub_path, .data = output }) catch |err| | |
| 138 | return step.fail(maker, "unable to write file {f}: {t}", .{ out_path, err }); | |
| 139 | ||
| 140 | maker.generatedPath(conf_ch.generated_dir).* = .{ | |
| 141 | .root_dir = cache_root, | |
| 142 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), | |
| 143 | }; | |
| 144 | ||
| 145 | try step.writeManifest(maker, &man); | |
| 146 | } | |
| 147 | ||
| 148 | fn ensureAllValuesUsed( | |
| 149 | maker: *Maker, | |
| 150 | step: *Step, | |
| 151 | value_map: *const ValueMap, | |
| 152 | src_path: Path, | |
| 153 | ) Step.ExtendedMakeError!void { | |
| 154 | var any_errors = false; | |
| 155 | for (value_map.keys(), value_map.values()) |name, used| { | |
| 156 | if (used) continue; | |
| 157 | try step.addError(maker, "{f}: config header value unused: {s}", .{ src_path, name }); | |
| 158 | any_errors = true; | |
| 159 | } | |
| 160 | if (any_errors) return error.MakeFailed; | |
| 161 | } | |
| 162 | ||
| 163 | fn renderAutoConfUndef( | |
| 164 | maker: *Maker, | |
| 165 | step: *Step, | |
| 166 | contents: []const u8, | |
| 167 | w: *Writer, | |
| 168 | value_pairs: []const Value.Pair, | |
| 169 | value_map: *ValueMap, | |
| 170 | src_path: Path, | |
| 171 | ) !void { | |
| 172 | const conf = &maker.scanned_config.configuration; | |
| 173 | ||
| 174 | try w.writeAll(c_generated_line); | |
| 175 | ||
| 176 | var any_errors = false; | |
| 177 | var line_index: u32 = 0; | |
| 178 | var line_it = std.mem.splitScalar(u8, contents, '\n'); | |
| 179 | while (line_it.next()) |line| : (line_index += 1) { | |
| 180 | if (!std.mem.startsWith(u8, line, "#")) { | |
| 181 | try w.writeAll(line); | |
| 182 | try w.writeByte('\n'); | |
| 183 | continue; | |
| 184 | } | |
| 185 | var it = std.mem.tokenizeAny(u8, line[1..], " \t\r"); | |
| 186 | const undef = it.next().?; | |
| 187 | if (!std.mem.eql(u8, undef, "undef")) { | |
| 188 | try w.writeAll(line); | |
| 189 | try w.writeByte('\n'); | |
| 190 | continue; | |
| 191 | } | |
| 192 | const name = it.next().?; | |
| 193 | const index = value_map.getIndex(name) orelse { | |
| 194 | try step.addError(maker, "{f}:{d}: unspecified config header value: {s}", .{ | |
| 195 | src_path, line_index + 1, name, | |
| 196 | }); | |
| 197 | any_errors = true; | |
| 198 | continue; | |
| 199 | }; | |
| 200 | value_map.values()[index] = true; // Set to used. | |
| 201 | try renderValueC(conf, w, name, value_pairs[index].index); | |
| 202 | } | |
| 203 | ||
| 204 | try ensureAllValuesUsed(maker, step, value_map, src_path); | |
| 205 | if (any_errors) return error.MakeFailed; | |
| 206 | } | |
| 207 | ||
| 208 | fn renderAutoconfAt( | |
| 209 | maker: *Maker, | |
| 210 | step: *Step, | |
| 211 | contents: []const u8, | |
| 212 | aw: *Writer.Allocating, | |
| 213 | value_pairs: []const Value.Pair, | |
| 214 | value_map: *const ValueMap, | |
| 215 | src_path: Path, | |
| 216 | ) !void { | |
| 217 | const w = &aw.writer; | |
| 218 | const conf = &maker.scanned_config.configuration; | |
| 219 | ||
| 220 | try w.writeAll(c_generated_line); | |
| 221 | ||
| 222 | var any_errors = false; | |
| 223 | var line_index: u32 = 0; | |
| 224 | var line_it = std.mem.splitScalar(u8, contents, '\n'); | |
| 225 | while (line_it.next()) |line| : (line_index += 1) { | |
| 226 | const last_line = line_it.index == line_it.buffer.len; | |
| 227 | ||
| 228 | const old_len = aw.written().len; | |
| 229 | expandVariablesAutoconfAt(w, line, conf, value_pairs, value_map) catch |err| switch (err) { | |
| 230 | error.MissingValue => { | |
| 231 | const name = aw.written()[old_len..]; | |
| 232 | defer aw.shrinkRetainingCapacity(old_len); | |
| 233 | try step.addError(maker, "{f}:{d}: error: unspecified config header value: {s}", .{ | |
| 234 | src_path, line_index + 1, name, | |
| 235 | }); | |
| 236 | any_errors = true; | |
| 237 | continue; | |
| 238 | }, | |
| 239 | else => { | |
| 240 | try step.addError(maker, "{f}:{d}: unable to substitute variable: error: {t}", .{ | |
| 241 | src_path, line_index + 1, err, | |
| 242 | }); | |
| 243 | any_errors = true; | |
| 244 | continue; | |
| 245 | }, | |
| 246 | }; | |
| 247 | if (!last_line) try w.writeByte('\n'); | |
| 248 | } | |
| 249 | ||
| 250 | try ensureAllValuesUsed(maker, step, value_map, src_path); | |
| 251 | if (any_errors) return error.MakeFailed; | |
| 252 | } | |
| 253 | ||
| 254 | fn renderCmake( | |
| 255 | arena: Allocator, | |
| 256 | maker: *Maker, | |
| 257 | step: *Step, | |
| 258 | contents: []const u8, | |
| 259 | w: *Writer, | |
| 260 | value_pairs: []const Value.Pair, | |
| 261 | value_map: *ValueMap, | |
| 262 | src_path: Path, | |
| 263 | ) !void { | |
| 264 | const conf = &maker.scanned_config.configuration; | |
| 265 | ||
| 266 | try w.writeAll(c_generated_line); | |
| 267 | ||
| 268 | var any_errors = false; | |
| 269 | var line_index: u32 = 0; | |
| 270 | var line_it = std.mem.splitScalar(u8, contents, '\n'); | |
| 271 | while (line_it.next()) |raw_line| : (line_index += 1) { | |
| 272 | const last_line = line_it.index == line_it.buffer.len; | |
| 273 | ||
| 274 | const line = expandVariablesCmake(arena, raw_line, conf, value_pairs, value_map) catch |err| switch (err) { | |
| 275 | error.InvalidCharacter => { | |
| 276 | try step.addError(maker, "{f}:{d}: invalid character in a variable name", .{ | |
| 277 | src_path, line_index + 1, | |
| 278 | }); | |
| 279 | any_errors = true; | |
| 280 | continue; | |
| 281 | }, | |
| 282 | else => { | |
| 283 | try step.addError(maker, "{f}:{d}: failed substituting variable: {t}", .{ | |
| 284 | src_path, line_index + 1, err, | |
| 285 | }); | |
| 286 | any_errors = true; | |
| 287 | continue; | |
| 288 | }, | |
| 289 | }; | |
| 290 | ||
| 291 | const line_start = std.mem.findNone(u8, line, " \t\r") orelse { | |
| 292 | try w.writeAll(line); | |
| 293 | if (!last_line) try w.writeByte('\n'); | |
| 294 | continue; | |
| 295 | }; | |
| 296 | const whitespace_prefix = line[0..line_start]; | |
| 297 | const trimmed_line = line[line_start..]; | |
| 298 | ||
| 299 | if (!std.mem.startsWith(u8, trimmed_line, "#")) { | |
| 300 | try w.writeAll(line); | |
| 301 | if (!last_line) try w.writeByte('\n'); | |
| 302 | continue; | |
| 303 | } | |
| 304 | ||
| 305 | var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r"); | |
| 306 | const cmakedefine = it.next().?; | |
| 307 | ||
| 308 | const booldefine = if (std.mem.eql(u8, cmakedefine, "cmakedefine01")) | |
| 309 | true | |
| 310 | else if (std.mem.eql(u8, cmakedefine, "cmakedefine")) | |
| 311 | false | |
| 312 | else { | |
| 313 | try w.writeAll(line); | |
| 314 | if (!last_line) try w.writeByte('\n'); | |
| 315 | continue; | |
| 316 | }; | |
| 317 | ||
| 318 | const name = it.next() orelse { | |
| 319 | try step.addError(maker, "{f}:{d}: error: missing define name", .{ src_path, line_index + 1 }); | |
| 320 | any_errors = true; | |
| 321 | continue; | |
| 322 | }; | |
| 323 | const orig_value: Value.Index = v: { | |
| 324 | const index = value_map.getIndex(name) orelse break :v if (booldefine) .int_0 else .undef; | |
| 325 | value_map.values()[index] = true; // Mark as used. | |
| 326 | break :v value_pairs[index].index; | |
| 327 | }; | |
| 328 | const value = switch (orig_value.unpack(conf)) { | |
| 329 | .bool => |b| if (!b) .undef else orig_value, | |
| 330 | inline .i64, .u64 => |i| if (i == 0) .undef else orig_value, | |
| 331 | .string => |s| if (s.len == 0) .undef else orig_value, | |
| 332 | else => orig_value, | |
| 333 | }; | |
| 334 | ||
| 335 | try w.writeAll(whitespace_prefix); | |
| 336 | ||
| 337 | if (booldefine) { | |
| 338 | try renderValueCBool(w, name, switch (value.unpack(conf)) { | |
| 339 | .undef, .defined => false, | |
| 340 | .bool => |b| b, | |
| 341 | inline .u64, .i64 => |i| i != 0, | |
| 342 | .string => |s| s.len != 0, | |
| 343 | .ident => false, | |
| 344 | }); | |
| 345 | } else if (value != .undef) { | |
| 346 | try renderValueCIdent(w, name, it.rest()); | |
| 347 | } else { | |
| 348 | try renderValueC(conf, w, name, value); | |
| 349 | } | |
| 350 | } | |
| 351 | ||
| 352 | try ensureAllValuesUsed(maker, step, value_map, src_path); | |
| 353 | if (any_errors) return error.MakeFailed; | |
| 354 | } | |
| 355 | ||
| 356 | fn renderBlank( | |
| 357 | conf: *const Configuration, | |
| 358 | w: *Writer, | |
| 359 | value_pairs: []const Value.Pair, | |
| 360 | value_map: *const ValueMap, | |
| 361 | include_path: []const u8, | |
| 362 | include_guard_override: ?[]const u8, | |
| 363 | ) !void { | |
| 364 | try w.writeAll(c_generated_line); | |
| 365 | ||
| 366 | const include_guard_fmt: IncludeGuardFmt = .{ | |
| 367 | .include_path = include_path, | |
| 368 | .override = include_guard_override, | |
| 369 | }; | |
| 370 | ||
| 371 | try w.print( | |
| 372 | \\#ifndef {[0]f} | |
| 373 | \\#define {[0]f} | |
| 374 | \\ | |
| 375 | , .{include_guard_fmt}); | |
| 376 | ||
| 377 | for (value_map.keys(), value_pairs) |name, pair| try renderValueC(conf, w, name, pair.index); | |
| 378 | ||
| 379 | try w.print( | |
| 380 | \\#endif /* {f} */ | |
| 381 | \\ | |
| 382 | , .{include_guard_fmt}); | |
| 383 | } | |
| 384 | ||
| 385 | const IncludeGuardFmt = struct { | |
| 386 | include_path: []const u8, | |
| 387 | override: ?[]const u8, | |
| 388 | ||
| 389 | pub fn format(this: @This(), w: *Writer) Writer.Error!void { | |
| 390 | if (this.override) |s| return w.writeAll(s); | |
| 391 | for (this.include_path) |byte| switch (byte) { | |
| 392 | 'a'...'z' => try w.writeByte(byte - 'a' + 'A'), | |
| 393 | 'A'...'Z', '0'...'9' => continue, | |
| 394 | else => try w.writeByte('_'), | |
| 395 | }; | |
| 396 | } | |
| 397 | }; | |
| 398 | ||
| 399 | fn renderNasm( | |
| 400 | conf: *const Configuration, | |
| 401 | w: *Writer, | |
| 402 | value_pairs: []const Value.Pair, | |
| 403 | value_map: *const ValueMap, | |
| 404 | ) !void { | |
| 405 | try w.writeAll(asm_generated_line); | |
| 406 | for (value_map.keys(), value_pairs) |name, pair| try renderValueNasm(conf, w, name, pair.index); | |
| 407 | } | |
| 408 | ||
| 409 | fn renderValueC(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void { | |
| 410 | switch (value.unpack(conf)) { | |
| 411 | .undef => try w.print("/* #undef {s} */\n", .{name}), | |
| 412 | .defined => try w.print("#define {s}\n", .{name}), | |
| 413 | .bool => |b| return renderValueCBool(w, name, b), | |
| 414 | inline .u64, .i64 => |i| try w.print("#define {s} {d}\n", .{ name, i }), | |
| 415 | .ident => |ident| return renderValueCIdent(w, name, ident), | |
| 416 | .string => |string| try w.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }), | |
| 417 | } | |
| 418 | } | |
| 419 | ||
| 420 | fn renderValueCIdent(w: *Writer, name: []const u8, ident: []const u8) Writer.Error!void { | |
| 421 | return w.print("#define {s} {s}\n", .{ name, ident }); | |
| 422 | } | |
| 423 | ||
| 424 | fn renderValueCBool(w: *Writer, name: []const u8, b: bool) Writer.Error!void { | |
| 425 | return w.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }); | |
| 426 | } | |
| 427 | ||
| 428 | fn renderValueNasm(conf: *const Configuration, w: *Writer, name: []const u8, value: Value.Index) !void { | |
| 429 | switch (value.unpack(conf)) { | |
| 430 | .undef => try w.print("; %undef {s}\n", .{name}), | |
| 431 | .defined => try w.print("%define {s}\n", .{name}), | |
| 432 | .bool => |b| try w.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }), | |
| 433 | inline .u64, .i64 => |i| try w.print("%define {s} {d}\n", .{ name, i }), | |
| 434 | .ident => |ident| try w.print("%define {s} {s}\n", .{ name, ident }), | |
| 435 | .string => |string| try w.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }), | |
| 436 | } | |
| 437 | } | |
| 438 | ||
| 439 | fn expandVariablesAutoconfAt( | |
| 440 | w: *Writer, | |
| 441 | contents: []const u8, | |
| 442 | conf: *const Configuration, | |
| 443 | value_pairs: []const Value.Pair, | |
| 444 | value_map: *const ValueMap, | |
| 445 | ) !void { | |
| 446 | const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; | |
| 447 | ||
| 448 | var curr: usize = 0; | |
| 449 | var source_offset: usize = 0; | |
| 450 | while (curr < contents.len) : (curr += 1) { | |
| 451 | if (contents[curr] != '@') continue; | |
| 452 | if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| { | |
| 453 | if (close_pos == curr + 1) { | |
| 454 | // closed immediately, preserve as a literal | |
| 455 | continue; | |
| 456 | } | |
| 457 | const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0; | |
| 458 | if (valid_varname_end != close_pos) { | |
| 459 | // contains invalid characters, preserve as a literal | |
| 460 | continue; | |
| 461 | } | |
| 462 | ||
| 463 | const key = contents[curr + 1 .. close_pos]; | |
| 464 | const index = value_map.getIndex(key) orelse { | |
| 465 | // Report the missing key to the caller. | |
| 466 | try w.writeAll(key); | |
| 467 | return error.MissingValue; | |
| 468 | }; | |
| 469 | const value = value_pairs[index].index; | |
| 470 | value_map.values()[index] = true; // Mark as used. | |
| 471 | try w.writeAll(contents[source_offset..curr]); | |
| 472 | switch (value.unpack(conf)) { | |
| 473 | .undef, .defined => {}, | |
| 474 | .bool => |b| try w.writeByte(@as(u8, '0') + @intFromBool(b)), | |
| 475 | inline .u64, .i64 => |i| try w.print("{d}", .{i}), | |
| 476 | .ident, .string => |s| try w.writeAll(s), | |
| 477 | } | |
| 478 | ||
| 479 | curr = close_pos; | |
| 480 | source_offset = close_pos + 1; | |
| 481 | } | |
| 482 | } | |
| 483 | ||
| 484 | try w.writeAll(contents[source_offset..]); | |
| 485 | } | |
| 486 | ||
| 487 | fn expandVariablesCmake( | |
| 488 | arena: Allocator, | |
| 489 | contents: []const u8, | |
| 490 | conf: *const Configuration, | |
| 491 | value_pairs: []const Value.Pair, | |
| 492 | value_map: *const ValueMap, | |
| 493 | ) ![]const u8 { | |
| 494 | var result: std.ArrayList(u8) = .empty; | |
| 495 | ||
| 496 | const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-"; | |
| 497 | const open_var = "${"; | |
| 498 | ||
| 499 | var curr: usize = 0; | |
| 500 | var source_offset: usize = 0; | |
| 501 | const Position = struct { | |
| 502 | source: usize, | |
| 503 | target: usize, | |
| 504 | }; | |
| 505 | var var_stack: std.ArrayList(Position) = .empty; | |
| 506 | loop: while (curr < contents.len) : (curr += 1) { | |
| 507 | switch (contents[curr]) { | |
| 508 | '@' => blk: { | |
| 509 | if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| { | |
| 510 | if (close_pos == curr + 1) { | |
| 511 | // closed immediately, preserve as a literal | |
| 512 | break :blk; | |
| 513 | } | |
| 514 | const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0; | |
| 515 | if (valid_varname_end != close_pos) { | |
| 516 | // contains invalid characters, preserve as a literal | |
| 517 | break :blk; | |
| 518 | } | |
| 519 | ||
| 520 | const key = contents[curr + 1 .. close_pos]; | |
| 521 | const index = value_map.getIndex(key) orelse return error.MissingValue; | |
| 522 | value_map.values()[index] = true; // Mark as used. | |
| 523 | const value = value_pairs[index].index; | |
| 524 | const missing = contents[source_offset..curr]; | |
| 525 | try result.appendSlice(arena, missing); | |
| 526 | switch (value.unpack(conf)) { | |
| 527 | .undef, .defined => {}, | |
| 528 | .bool => |b| try result.append(arena, if (b) '1' else '0'), | |
| 529 | inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}), | |
| 530 | .ident, .string => |s| try result.appendSlice(arena, s), | |
| 531 | } | |
| 532 | ||
| 533 | curr = close_pos; | |
| 534 | source_offset = close_pos + 1; | |
| 535 | ||
| 536 | continue :loop; | |
| 537 | } | |
| 538 | }, | |
| 539 | '$' => blk: { | |
| 540 | const next = curr + 1; | |
| 541 | if (next == contents.len or contents[next] != '{') { | |
| 542 | // no open bracket detected, preserve as a literal | |
| 543 | break :blk; | |
| 544 | } | |
| 545 | const missing = contents[source_offset..curr]; | |
| 546 | try result.appendSlice(arena, missing); | |
| 547 | try result.appendSlice(arena, open_var); | |
| 548 | ||
| 549 | source_offset = curr + open_var.len; | |
| 550 | curr = next; | |
| 551 | try var_stack.append(arena, .{ | |
| 552 | .source = curr, | |
| 553 | .target = result.items.len - open_var.len, | |
| 554 | }); | |
| 555 | ||
| 556 | continue :loop; | |
| 557 | }, | |
| 558 | '}' => blk: { | |
| 559 | if (var_stack.items.len == 0) { | |
| 560 | // no open bracket, preserve as a literal | |
| 561 | break :blk; | |
| 562 | } | |
| 563 | const open_pos = var_stack.pop().?; | |
| 564 | if (source_offset == open_pos.source) { | |
| 565 | source_offset += open_var.len; | |
| 566 | } | |
| 567 | const missing = contents[source_offset..curr]; | |
| 568 | try result.appendSlice(arena, missing); | |
| 569 | ||
| 570 | const key_start = open_pos.target + open_var.len; | |
| 571 | const key = result.items[key_start..]; | |
| 572 | if (key.len == 0) { | |
| 573 | return error.MissingKey; | |
| 574 | } | |
| 575 | const index = value_map.getIndex(key) orelse return error.MissingValue; | |
| 576 | value_map.values()[index] = true; // Mark as used. | |
| 577 | const value = value_pairs[index].index; | |
| 578 | result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len); | |
| 579 | switch (value.unpack(conf)) { | |
| 580 | .undef, .defined => {}, | |
| 581 | .bool => |b| try result.append(arena, if (b) '1' else '0'), | |
| 582 | inline .i64, .u64 => |i| try result.print(arena, "{d}", .{i}), | |
| 583 | .ident, .string => |s| try result.appendSlice(arena, s), | |
| 584 | } | |
| 585 | ||
| 586 | source_offset = curr + 1; | |
| 587 | ||
| 588 | continue :loop; | |
| 589 | }, | |
| 590 | '\\' => { | |
| 591 | // backslash is not considered a special character | |
| 592 | continue :loop; | |
| 593 | }, | |
| 594 | else => {}, | |
| 595 | } | |
| 596 | ||
| 597 | if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) { | |
| 598 | return error.InvalidCharacter; | |
| 599 | } | |
| 600 | } | |
| 601 | ||
| 602 | if (source_offset != contents.len) { | |
| 603 | const missing = contents[source_offset..]; | |
| 604 | try result.appendSlice(arena, missing); | |
| 605 | } | |
| 606 | ||
| 607 | try result.shrinkToLen(arena); | |
| 608 | ||
| 609 | return result.toOwnedSliceAssert(); | |
| 610 | } |
lib/compiler/Maker/Step/FindProgram.zig created+120| ... | ... | @@ -0,0 +1,120 @@ |
| 1 | const FindProgram = @This(); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 6 | const Configuration = std.Build.Configuration; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | const Step = @import("../Step.zig"); | |
| 10 | const Maker = @import("../../Maker.zig"); | |
| 11 | ||
| 12 | pub fn make( | |
| 13 | find_program: *FindProgram, | |
| 14 | step_index: Configuration.Step.Index, | |
| 15 | maker: *Maker, | |
| 16 | progress_node: std.Progress.Node, | |
| 17 | ) Step.ExtendedMakeError!void { | |
| 18 | _ = find_program; | |
| 19 | _ = progress_node; | |
| 20 | const graph = maker.graph; | |
| 21 | const step = maker.stepByIndex(step_index); | |
| 22 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 23 | const conf = &maker.scanned_config.configuration; | |
| 24 | const conf_step = step_index.ptr(conf); | |
| 25 | const conf_fp = conf_step.extended.get(conf.extra).find_program; | |
| 26 | const found_path = conf_fp.found_path; | |
| 27 | const names = conf_fp.names.slice(conf); | |
| 28 | ||
| 29 | // In case we fail at the end. | |
| 30 | var err_msg: std.ArrayList(u8) = .empty; | |
| 31 | try err_msg.appendSlice(arena, "program not found. searched paths:\n"); | |
| 32 | ||
| 33 | for (names) |name_index| { | |
| 34 | const name = name_index.slice(conf); | |
| 35 | ||
| 36 | if (Io.Dir.path.isAbsolute(name)) { | |
| 37 | if (try checkCandidate(maker, step, found_path, &err_msg, name)) return; | |
| 38 | ||
| 39 | continue; | |
| 40 | } | |
| 41 | ||
| 42 | for (graph.search_prefixes.items) |search_prefix| { | |
| 43 | const full_path = try Io.Dir.path.join(arena, &.{ search_prefix, "bin", name }); | |
| 44 | ||
| 45 | if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return; | |
| 46 | } | |
| 47 | } | |
| 48 | ||
| 49 | if (graph.environ_map.get("PATH")) |PATH| { | |
| 50 | for (names) |name_index| { | |
| 51 | const name = name_index.slice(conf); | |
| 52 | ||
| 53 | var it = std.mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter); | |
| 54 | while (it.next()) |p| { | |
| 55 | const full_path = try Io.Dir.path.join(arena, &.{ p, name }); | |
| 56 | ||
| 57 | if (try checkCandidate(maker, step, found_path, &err_msg, full_path)) return; | |
| 58 | } | |
| 59 | } | |
| 60 | } | |
| 61 | ||
| 62 | assert(err_msg.items[err_msg.items.len - 1] == '\n'); | |
| 63 | const chopped = err_msg.items[0 .. err_msg.items.len - 1]; | |
| 64 | try step.result_error_msgs.append(arena, chopped); | |
| 65 | return error.MakeFailed; | |
| 66 | } | |
| 67 | ||
| 68 | fn checkCandidate( | |
| 69 | maker: *Maker, | |
| 70 | step: *Step, | |
| 71 | found_path: Configuration.GeneratedFileIndex, | |
| 72 | err_msg: *std.ArrayList(u8), | |
| 73 | full_path: []const u8, | |
| 74 | ) !bool { | |
| 75 | const graph = maker.graph; | |
| 76 | const arena = graph.arena; // TODO don't leak into process arena | |
| 77 | const io = graph.io; | |
| 78 | ||
| 79 | if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| { | |
| 80 | maker.generatedPath(found_path).* = .initCwd(full_path); | |
| 81 | return true; | |
| 82 | } else |err| switch (err) { | |
| 83 | error.Canceled => |e| return e, | |
| 84 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { | |
| 85 | try err_msg.print(arena, "{t} {s}\n", .{ e, full_path }); | |
| 86 | }, | |
| 87 | else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ full_path, e }), | |
| 88 | } | |
| 89 | ||
| 90 | if (builtin.os.tag == .windows) { | |
| 91 | if (graph.environ_map.get("PATHEXT")) |PATHEXT| { | |
| 92 | var it = std.mem.tokenizeScalar(u8, PATHEXT, Io.Dir.path.delimiter); | |
| 93 | while (it.next()) |ext| { | |
| 94 | if (!supportedWindowsProgramExtension(ext)) continue; | |
| 95 | ||
| 96 | const extended_path = try std.mem.concat(arena, u8, &.{ full_path, ext }); | |
| 97 | ||
| 98 | if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| { | |
| 99 | maker.generatedPath(found_path).* = .initCwd(extended_path); | |
| 100 | return true; | |
| 101 | } else |err| switch (err) { | |
| 102 | error.Canceled => |e| return e, | |
| 103 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { | |
| 104 | try err_msg.print(arena, "{t} {s}\n", .{ e, extended_path }); | |
| 105 | }, | |
| 106 | else => |e| return step.fail(maker, "failed accessing {s}: {t}", .{ extended_path, e }), | |
| 107 | } | |
| 108 | } | |
| 109 | } | |
| 110 | } | |
| 111 | ||
| 112 | return false; | |
| 113 | } | |
| 114 | ||
| 115 | fn supportedWindowsProgramExtension(ext: []const u8) bool { | |
| 116 | inline for (@typeInfo(std.process.WindowsExtension).@"enum".fields) |field| { | |
| 117 | if (std.ascii.eqlIgnoreCase(ext, "." ++ field.name)) return true; | |
| 118 | } | |
| 119 | return false; | |
| 120 | } |
lib/compiler/Maker/Step/Fmt.zig created+65| ... | ... | @@ -0,0 +1,65 @@ |
| 1 | const Fmt = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Configuration = std.Build.Configuration; | |
| 5 | ||
| 6 | const Step = @import("../Step.zig"); | |
| 7 | const Maker = @import("../../Maker.zig"); | |
| 8 | ||
| 9 | /// Persisted to reuse memory on subsequent calls to `make`. | |
| 10 | argv: std.ArrayList([]const u8) = .empty, | |
| 11 | ||
| 12 | pub fn make( | |
| 13 | fmt: *Fmt, | |
| 14 | step_index: Configuration.Step.Index, | |
| 15 | maker: *Maker, | |
| 16 | progress_node: std.Progress.Node, | |
| 17 | ) Step.ExtendedMakeError!void { | |
| 18 | const graph = maker.graph; | |
| 19 | const step = maker.stepByIndex(step_index); | |
| 20 | const gpa = maker.gpa; | |
| 21 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 22 | const argv = &fmt.argv; | |
| 23 | const conf = &maker.scanned_config.configuration; | |
| 24 | const conf_step = step_index.ptr(conf); | |
| 25 | const conf_fmt = conf_step.extended.get(conf.extra).fmt; | |
| 26 | const paths = conf_fmt.paths.slice; | |
| 27 | const exclude_paths = conf_fmt.exclude_paths.slice; | |
| 28 | ||
| 29 | argv.clearRetainingCapacity(); | |
| 30 | try argv.ensureUnusedCapacity(gpa, 2 + 1 + paths.len + 2 * exclude_paths.len); | |
| 31 | ||
| 32 | argv.appendAssumeCapacity(graph.zig_exe); | |
| 33 | argv.appendAssumeCapacity("fmt"); | |
| 34 | ||
| 35 | if (conf_fmt.flags.check) | |
| 36 | argv.appendAssumeCapacity("--check"); | |
| 37 | ||
| 38 | for (paths) |lp| | |
| 39 | argv.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, step_index)); | |
| 40 | ||
| 41 | for (exclude_paths) |lp| { | |
| 42 | argv.appendAssumeCapacity("--exclude"); | |
| 43 | argv.appendAssumeCapacity(try maker.resolveLazyPathIndexAbs(arena, lp, step_index)); | |
| 44 | } | |
| 45 | ||
| 46 | const run_result = step.captureChildProcess(maker, .{ | |
| 47 | .progress_node = progress_node, | |
| 48 | .argv = argv.items, | |
| 49 | .allow_failure = false, | |
| 50 | }) catch |err| switch (err) { | |
| 51 | error.FileNotFound => unreachable, | |
| 52 | else => |e| return e, | |
| 53 | }; | |
| 54 | ||
| 55 | if (conf_fmt.flags.check) switch (run_result.term) { | |
| 56 | .exited => |code| if (code != 0 and run_result.stdout.len != 0) { | |
| 57 | var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n'); | |
| 58 | while (it.next()) |bad_file_name| { | |
| 59 | try step.addError(maker, "{s}: non-conforming formatting", .{bad_file_name}); | |
| 60 | } | |
| 61 | }, | |
| 62 | else => {}, | |
| 63 | }; | |
| 64 | try step.handleChildProcessTerm(maker, run_result.term); | |
| 65 | } |
lib/compiler/Maker/Step/InstallArtifact.zig created+141| ... | ... | @@ -0,0 +1,141 @@ |
| 1 | const InstallArtifact = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Configuration = std.Build.Configuration; | |
| 6 | const assert = std.debug.assert; | |
| 7 | ||
| 8 | const Step = @import("../Step.zig"); | |
| 9 | const Maker = @import("../../Maker.zig"); | |
| 10 | ||
| 11 | pub fn make( | |
| 12 | install_artifact: *InstallArtifact, | |
| 13 | step_index: Configuration.Step.Index, | |
| 14 | maker: *Maker, | |
| 15 | progress_node: std.Progress.Node, | |
| 16 | ) Step.ExtendedMakeError!void { | |
| 17 | _ = install_artifact; | |
| 18 | _ = progress_node; | |
| 19 | const step = maker.stepByIndex(step_index); | |
| 20 | const conf = &maker.scanned_config.configuration; | |
| 21 | const graph = maker.graph; | |
| 22 | const gpa = maker.gpa; | |
| 23 | const arena = graph.arena; // TODO don't leak into process arena | |
| 24 | const io = graph.io; | |
| 25 | const conf_step = step_index.ptr(conf); | |
| 26 | const conf_ia = conf_step.extended.get(conf.extra).install_artifact; | |
| 27 | const compile_step_index = conf_step.deps.get(conf).steps.slice[0]; | |
| 28 | const conf_comp_step = compile_step_index.ptr(conf); | |
| 29 | const conf_comp = conf_comp_step.extended.get(conf.extra).compile; | |
| 30 | const root_module = conf_comp.root_module.get(conf); | |
| 31 | const target = root_module.resolved_target.get(conf).?.result.get(conf); | |
| 32 | ||
| 33 | var all_cached = true; | |
| 34 | ||
| 35 | if (conf_ia.bin_dir.value) |bin_dir| { | |
| 36 | if (conf_comp.generated_bin.value) |generated_bin| { | |
| 37 | const bin_sub_path = if (conf_ia.bin_sub_path.value) |s| s.slice(conf) else try std.zig.binNameAlloc(arena, .{ | |
| 38 | .root_name = conf_comp.root_name.slice(conf), | |
| 39 | .cpu_arch = target.flags.cpu_arch.unwrap().?, | |
| 40 | .os_tag = target.flags.os_tag.unwrap().?, | |
| 41 | .ofmt = target.flags.object_format.unwrap().?, | |
| 42 | .abi = target.flags.abi.unwrap().?, | |
| 43 | .output_mode = conf_comp.flags3.kind.toOutputMode(), | |
| 44 | .link_mode = conf_comp.flags2.linkage.unwrap(), | |
| 45 | .version = v: { | |
| 46 | const string = conf_comp.version.value orelse break :v null; | |
| 47 | const slice = string.slice(conf); | |
| 48 | break :v std.SemanticVersion.parse(slice) catch @panic("bad semver string"); | |
| 49 | }, | |
| 50 | }); | |
| 51 | const dest_dir = try maker.resolveInstallDir(arena, bin_dir); | |
| 52 | const dest_path = try dest_dir.join(arena, bin_sub_path); | |
| 53 | const src_path = maker.generatedPath(generated_bin).*; | |
| 54 | const p = try maker.installPath(arena, src_path, dest_path, step_index); | |
| 55 | all_cached = all_cached and p == .fresh; | |
| 56 | ||
| 57 | if (conf_ia.flags.dylib_symlinks) | |
| 58 | try maker.installSymLinks(arena, dest_path, compile_step_index, step_index); | |
| 59 | ||
| 60 | const make_comp_step = maker.stepByIndex(compile_step_index); | |
| 61 | const make_comp = &make_comp_step.extended.compile; | |
| 62 | make_comp.installed_path = dest_path; | |
| 63 | } | |
| 64 | } | |
| 65 | ||
| 66 | if (conf_ia.implib_dir.value) |implib_dir| { | |
| 67 | if (conf_comp.generated_implib.value) |generated_implib| { | |
| 68 | const p = try maker.installGenerated(arena, generated_implib, implib_dir, step_index); | |
| 69 | all_cached = all_cached and p == .fresh; | |
| 70 | } | |
| 71 | } | |
| 72 | ||
| 73 | if (conf_ia.pdb_dir.value) |pdb_dir| { | |
| 74 | if (conf_comp.generated_pdb.value) |generated_pdb| { | |
| 75 | const p = try maker.installGenerated(arena, generated_pdb, pdb_dir, step_index); | |
| 76 | all_cached = all_cached and p == .fresh; | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | if (conf_ia.h_dir.value) |h_dir| { | |
| 81 | const h_prefix = try maker.resolveInstallDir(arena, h_dir); | |
| 82 | ||
| 83 | if (conf_comp.generated_h.value) |generated_h| { | |
| 84 | const p = try maker.installGenerated(arena, generated_h, h_dir, step_index); | |
| 85 | all_cached = all_cached and p == .fresh; | |
| 86 | } | |
| 87 | ||
| 88 | for (conf_comp.installed_headers.slice) |installation| switch (installation.get(conf.extra)) { | |
| 89 | .file => |file| { | |
| 90 | const src_path = try maker.resolveLazyPathIndex(arena, file.source, step_index); | |
| 91 | const dest_path = try h_prefix.join(arena, file.dest_sub_path.slice(conf)); | |
| 92 | const p = try maker.installPath(arena, src_path, dest_path, step_index); | |
| 93 | all_cached = all_cached and p == .fresh; | |
| 94 | }, | |
| 95 | .directory => |dir| { | |
| 96 | const src_dir_path = try maker.resolveLazyPathIndex(arena, dir.source, step_index); | |
| 97 | const full_h_prefix = try h_prefix.join(arena, dir.dest_sub_path.slice(conf)); | |
| 98 | ||
| 99 | var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { | |
| 100 | return step.fail(maker, "unable to open source directory {f}: {t}", .{ src_dir_path, err }); | |
| 101 | }; | |
| 102 | defer src_dir.close(io); | |
| 103 | ||
| 104 | var it = try src_dir.walk(gpa); | |
| 105 | defer it.deinit(); | |
| 106 | next_entry: while (it.next(io) catch |err| switch (err) { | |
| 107 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 108 | else => |e| return step.fail(maker, "failed to iterate directory {f}: {t}", .{ src_dir_path, e }), | |
| 109 | }) |entry| { | |
| 110 | for (dir.exclude_extensions.slice) |ext| { | |
| 111 | if (std.mem.endsWith(u8, entry.path, ext.slice(conf))) continue :next_entry; | |
| 112 | } | |
| 113 | if (dir.flags.include_extensions) { | |
| 114 | for (dir.include_extensions.slice) |inc| { | |
| 115 | if (std.mem.endsWith(u8, entry.path, inc.slice(conf))) break; | |
| 116 | } else { | |
| 117 | continue :next_entry; | |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | const full_dest_path = try full_h_prefix.join(arena, entry.path); | |
| 122 | switch (entry.kind) { | |
| 123 | .directory => { | |
| 124 | const p = try maker.installDir(arena, full_dest_path, step_index); | |
| 125 | all_cached = all_cached and p == .existed; | |
| 126 | }, | |
| 127 | .file => { | |
| 128 | const entry_dir_path = try maker.resolveLazyPathIndex(arena, dir.source, step_index); | |
| 129 | const entry_path = try entry_dir_path.join(arena, entry.path); | |
| 130 | const p = try maker.installPath(arena, entry_path, full_dest_path, step_index); | |
| 131 | all_cached = all_cached and p == .fresh; | |
| 132 | }, | |
| 133 | else => continue, | |
| 134 | } | |
| 135 | } | |
| 136 | }, | |
| 137 | }; | |
| 138 | } | |
| 139 | ||
| 140 | step.result_cached = all_cached; | |
| 141 | } |
lib/compiler/Maker/Step/InstallDir.zig created+99| ... | ... | @@ -0,0 +1,99 @@ |
| 1 | const InstallDir = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const log = std.log; | |
| 6 | const Configuration = std.Build.Configuration; | |
| 7 | const endsWith = std.mem.endsWith; | |
| 8 | ||
| 9 | const Step = @import("../Step.zig"); | |
| 10 | const Maker = @import("../../Maker.zig"); | |
| 11 | ||
| 12 | pub fn make( | |
| 13 | install_dir: *InstallDir, | |
| 14 | step_index: Configuration.Step.Index, | |
| 15 | maker: *Maker, | |
| 16 | progress_node: std.Progress.Node, | |
| 17 | ) Step.ExtendedMakeError!void { | |
| 18 | _ = install_dir; | |
| 19 | const graph = maker.graph; | |
| 20 | const gpa = maker.gpa; | |
| 21 | const arena = maker.graph.arena; // TODO don't leak into process arena | |
| 22 | const io = graph.io; | |
| 23 | const step = maker.stepByIndex(step_index); | |
| 24 | const conf = &maker.scanned_config.configuration; | |
| 25 | const conf_step = step_index.ptr(conf); | |
| 26 | const conf_id = conf_step.extended.get(conf.extra).install_dir; | |
| 27 | ||
| 28 | step.clearWatchInputs(maker); | |
| 29 | ||
| 30 | const dest_parent_path = try maker.resolveInstallDir(arena, conf_id.dest_dir); | |
| 31 | const dest_prefix = if (conf_id.dest_sub_path.value) |s| | |
| 32 | try dest_parent_path.join(arena, s.slice(conf)) | |
| 33 | else | |
| 34 | dest_parent_path; | |
| 35 | const src_dir_lazy_path = conf_id.source_dir.get(conf); | |
| 36 | const src_dir_path = try maker.resolveLazyPath(arena, src_dir_lazy_path, step_index); | |
| 37 | const need_derived_inputs = try step.addDirectoryWatchInput(maker, src_dir_lazy_path); | |
| 38 | ||
| 39 | var src_dir = src_dir_path.root_dir.handle.openDir( | |
| 40 | io, | |
| 41 | src_dir_path.subPathOrDot(), | |
| 42 | .{ .iterate = true }, | |
| 43 | ) catch |err| return step.fail(maker, "failed opening source directory {f}: {t}", .{ src_dir_path, err }); | |
| 44 | defer src_dir.close(io); | |
| 45 | ||
| 46 | const exclude_extensions = conf_id.exclude_extensions.slice; | |
| 47 | const include_extensions: ?[]const Configuration.String = if (conf_id.flags.include_extensions_active) | |
| 48 | conf_id.include_extensions.slice | |
| 49 | else | |
| 50 | null; | |
| 51 | const blank_extensions = conf_id.blank_extensions.slice; | |
| 52 | ||
| 53 | var all_cached = true; | |
| 54 | var it = try src_dir.walk(gpa); | |
| 55 | defer it.deinit(); | |
| 56 | next_entry: while (it.next(io) catch |err| switch (err) { | |
| 57 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 58 | else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }), | |
| 59 | }) |entry| { | |
| 60 | for (exclude_extensions) |ext| { | |
| 61 | if (endsWith(u8, entry.path, ext.slice(conf))) continue :next_entry; | |
| 62 | } | |
| 63 | if (include_extensions) |includes| { | |
| 64 | for (includes) |inc| { | |
| 65 | if (endsWith(u8, entry.path, inc.slice(conf))) break; | |
| 66 | } else { | |
| 67 | continue :next_entry; | |
| 68 | } | |
| 69 | } | |
| 70 | ||
| 71 | const dest_path = try dest_prefix.join(arena, entry.path); | |
| 72 | switch (entry.kind) { | |
| 73 | .directory => { | |
| 74 | if (need_derived_inputs) { | |
| 75 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 76 | try step.addDirectoryWatchInputFromPath(maker, entry_path); | |
| 77 | } | |
| 78 | const p = try maker.installDir(arena, dest_path, step_index); | |
| 79 | all_cached = all_cached and p == .existed; | |
| 80 | }, | |
| 81 | .file => { | |
| 82 | for (blank_extensions) |ext| { | |
| 83 | if (endsWith(u8, entry.path, ext.slice(conf))) { | |
| 84 | try maker.truncatePath(arena, dest_path, step_index); | |
| 85 | continue :next_entry; | |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 90 | const p = try maker.installPath(arena, entry_path, dest_path, step_index); | |
| 91 | all_cached = all_cached and p == .fresh; | |
| 92 | progress_node.completeOne(); | |
| 93 | }, | |
| 94 | else => continue, | |
| 95 | } | |
| 96 | } | |
| 97 | ||
| 98 | step.result_cached = all_cached; | |
| 99 | } |
lib/compiler/Maker/Step/InstallFile.zig created+26| ... | ... | @@ -0,0 +1,26 @@ |
| 1 | const InstallFile = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Configuration = std.Build.Configuration; | |
| 5 | ||
| 6 | const Step = @import("../Step.zig"); | |
| 7 | const Maker = @import("../../Maker.zig"); | |
| 8 | ||
| 9 | pub fn make( | |
| 10 | install_file: *InstallFile, | |
| 11 | step_index: Configuration.Step.Index, | |
| 12 | maker: *Maker, | |
| 13 | progress_node: std.Progress.Node, | |
| 14 | ) Step.ExtendedMakeError!void { | |
| 15 | _ = install_file; | |
| 16 | _ = progress_node; | |
| 17 | const arena = maker.graph.arena; // TODO don't leak into process arena | |
| 18 | const step = maker.stepByIndex(step_index); | |
| 19 | const conf = &maker.scanned_config.configuration; | |
| 20 | const conf_step = step_index.ptr(conf); | |
| 21 | const conf_if = conf_step.extended.get(conf.extra).install_file; | |
| 22 | ||
| 23 | try step.singleUnchangingWatchInput(maker, arena, conf_if.source.get(conf)); | |
| 24 | const p = try maker.installLazyPathSub(arena, conf_if.source, conf_if.dest_dir, conf_if.dest_sub_path.slice(conf), step_index); | |
| 25 | step.result_cached = p == .fresh; | |
| 26 | } |
lib/compiler/Maker/Step/ObjCopy.zig created+173| ... | ... | @@ -0,0 +1,173 @@ |
| 1 | const ObjCopy = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Path = std.Build.Cache.Path; | |
| 6 | const allocPrint = std.fmt.allocPrint; | |
| 7 | const Configuration = std.Build.Configuration; | |
| 8 | ||
| 9 | const Step = @import("../Step.zig"); | |
| 10 | const Maker = @import("../../Maker.zig"); | |
| 11 | ||
| 12 | pub fn make( | |
| 13 | obj_copy: *ObjCopy, | |
| 14 | step_index: Configuration.Step.Index, | |
| 15 | maker: *Maker, | |
| 16 | progress_node: std.Progress.Node, | |
| 17 | ) Step.ExtendedMakeError!void { | |
| 18 | _ = obj_copy; | |
| 19 | const graph = maker.graph; | |
| 20 | const arena = maker.graph.arena; // TODO don't leak into process arena | |
| 21 | const io = graph.io; | |
| 22 | const step = maker.stepByIndex(step_index); | |
| 23 | const conf = &maker.scanned_config.configuration; | |
| 24 | const conf_step = step_index.ptr(conf); | |
| 25 | const conf_oc = conf_step.extended.get(conf.extra).obj_copy; | |
| 26 | const cache_root = graph.local_cache_root; | |
| 27 | const input_lazy_path = conf_oc.input_file.get(conf); | |
| 28 | const only_section: ?[]const u8 = if (conf_oc.only_section.value) |s| s.slice(conf) else null; | |
| 29 | const opt_basename: ?[]const u8 = if (conf_oc.basename.value) |s| s.slice(conf) else null; | |
| 30 | const opt_debug_basename: ?[]const u8 = if (conf_oc.debug_basename.value) |s| s.slice(conf) else null; | |
| 31 | ||
| 32 | try step.singleUnchangingWatchInput(maker, arena, input_lazy_path); | |
| 33 | ||
| 34 | var man = graph.cache.obtain(); | |
| 35 | defer man.deinit(); | |
| 36 | ||
| 37 | const input_path = try maker.resolveLazyPath(arena, input_lazy_path, step_index); | |
| 38 | _ = try man.addFilePath(input_path, null); | |
| 39 | man.hash.addOptionalBytes(only_section); | |
| 40 | man.hash.addOptionalBytes(opt_basename); | |
| 41 | man.hash.addOptionalBytes(opt_debug_basename); | |
| 42 | man.hash.addOptional(conf_oc.pad_to.value); | |
| 43 | man.hash.add(conf_oc.flags.format); | |
| 44 | man.hash.add(conf_oc.flags.compress_debug); | |
| 45 | man.hash.add(conf_oc.flags.strip); | |
| 46 | man.hash.add(conf_oc.debug_file.value != null); | |
| 47 | ||
| 48 | const basename = opt_basename orelse Io.Dir.path.basename(input_path.sub_path); | |
| 49 | ||
| 50 | if (try step.cacheHit(maker, &man)) { | |
| 51 | // Cache hit, skip subprocess execution. | |
| 52 | const digest = man.final(); | |
| 53 | maker.generatedPath(conf_oc.output_file).* = .{ | |
| 54 | .root_dir = cache_root, | |
| 55 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), | |
| 56 | }; | |
| 57 | if (conf_oc.debug_file.value) |debug_file| { | |
| 58 | const debug_basename = opt_debug_basename orelse try allocPrint(arena, "{s}.debug", .{ | |
| 59 | Io.Dir.path.basename(input_path.sub_path), | |
| 60 | }); | |
| 61 | maker.generatedPath(debug_file).* = .{ | |
| 62 | .root_dir = cache_root, | |
| 63 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, debug_basename }), | |
| 64 | }; | |
| 65 | } | |
| 66 | return; | |
| 67 | } | |
| 68 | ||
| 69 | // We don't find out more input files while executing objcopy so we can | |
| 70 | // already obtain the digest and use it directly as the output path. | |
| 71 | const digest = man.final(); | |
| 72 | const dest_path: Path = .{ | |
| 73 | .root_dir = cache_root, | |
| 74 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), | |
| 75 | }; | |
| 76 | const dest_dirname = dest_path.dirname().?; | |
| 77 | dest_dirname.root_dir.handle.createDirPath(io, dest_dirname.sub_path) catch |err| | |
| 78 | return step.fail(maker, "failed to create path {f}: {t}", .{ dest_dirname, err }); | |
| 79 | ||
| 80 | var argv: std.ArrayList([]const u8) = .empty; | |
| 81 | try argv.ensureUnusedCapacity(arena, 11); | |
| 82 | ||
| 83 | argv.addManyAsArrayAssumeCapacity(2).* = .{ graph.zig_exe, "objcopy" }; | |
| 84 | ||
| 85 | if (only_section) |s| argv.addManyAsArrayAssumeCapacity(2).* = .{ "-j", s }; | |
| 86 | ||
| 87 | switch (conf_oc.flags.strip) { | |
| 88 | .none => {}, | |
| 89 | .debug => argv.appendAssumeCapacity("--strip-debug"), | |
| 90 | .debug_and_symbols => argv.appendAssumeCapacity("--strip-all"), | |
| 91 | } | |
| 92 | ||
| 93 | if (conf_oc.pad_to.value) |pad_to| { | |
| 94 | argv.addManyAsArrayAssumeCapacity(2).* = .{ | |
| 95 | "--pad-to", try allocPrint(arena, "{d}", .{pad_to}), | |
| 96 | }; | |
| 97 | } | |
| 98 | ||
| 99 | switch (conf_oc.flags.format) { | |
| 100 | .default => {}, | |
| 101 | else => |t| argv.addManyAsArrayAssumeCapacity(2).* = .{ "-O", @tagName(t) }, | |
| 102 | } | |
| 103 | ||
| 104 | if (conf_oc.flags.compress_debug) | |
| 105 | argv.appendAssumeCapacity("--compress-debug-sections"); | |
| 106 | ||
| 107 | if (conf_oc.debug_file.value) |debug_file| { | |
| 108 | const debug_basename = opt_debug_basename orelse try allocPrint(arena, "{s}.debug", .{ | |
| 109 | Io.Dir.path.basename(input_path.sub_path), | |
| 110 | }); | |
| 111 | const debug_dest_path: Path = .{ | |
| 112 | .root_dir = cache_root, | |
| 113 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, debug_basename }), | |
| 114 | }; | |
| 115 | argv.appendAssumeCapacity(try allocPrint(arena, "--extract-to={f}", .{debug_dest_path})); | |
| 116 | maker.generatedPath(debug_file).* = debug_dest_path; | |
| 117 | } | |
| 118 | ||
| 119 | try argv.ensureUnusedCapacity(arena, conf_oc.add_section.slice.len * 2); | |
| 120 | ||
| 121 | for (conf_oc.add_section.slice) |section| { | |
| 122 | argv.appendAssumeCapacity("--add-section"); | |
| 123 | argv.appendAssumeCapacity(try allocPrint(arena, "{s}={f}", .{ | |
| 124 | section.section_name.slice(conf), | |
| 125 | try maker.resolveLazyPathIndex(arena, section.file_path, step_index), | |
| 126 | })); | |
| 127 | } | |
| 128 | ||
| 129 | for (conf_oc.update_section.slice) |update| { | |
| 130 | const name = update.section_name.slice(conf); | |
| 131 | ||
| 132 | try argv.ensureUnusedCapacity(arena, 4); | |
| 133 | ||
| 134 | if (update.flags.alignment.toBytes()) |a| { | |
| 135 | argv.appendAssumeCapacity("--set-section-alignment"); | |
| 136 | argv.appendAssumeCapacity(try allocPrint(arena, "{s}={d}", .{ name, a })); | |
| 137 | } | |
| 138 | ||
| 139 | const f = update.flags.section_flags; | |
| 140 | if (f != Configuration.Step.ObjCopy.SectionFlags.default) { | |
| 141 | // trailing comma is allowed | |
| 142 | argv.appendAssumeCapacity("--set-section-flags"); | |
| 143 | argv.appendAssumeCapacity(try allocPrint(arena, "{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{ | |
| 144 | name, | |
| 145 | if (f.alloc) "alloc," else "", | |
| 146 | if (f.contents) "contents," else "", | |
| 147 | if (f.load) "load," else "", | |
| 148 | if (f.readonly) "readonly," else "", | |
| 149 | if (f.code) "code," else "", | |
| 150 | if (f.exclude) "exclude," else "", | |
| 151 | if (f.large) "large," else "", | |
| 152 | if (f.merge) "merge," else "", | |
| 153 | if (f.strings) "strings," else "", | |
| 154 | })); | |
| 155 | } | |
| 156 | } | |
| 157 | ||
| 158 | argv.appendAssumeCapacity(try allocPrint(arena, "{f}", .{input_path})); | |
| 159 | argv.appendAssumeCapacity(try allocPrint(arena, "{f}", .{dest_path})); | |
| 160 | ||
| 161 | argv.appendAssumeCapacity("--listen=-"); | |
| 162 | _ = Step.evalZigProcess(step_index, maker, argv.items, progress_node, false) catch |err| switch (err) { | |
| 163 | error.NeedCompileErrorCheck => unreachable, | |
| 164 | else => |e| return e, | |
| 165 | }; | |
| 166 | ||
| 167 | maker.generatedPath(conf_oc.output_file).* = dest_path; | |
| 168 | ||
| 169 | step.writeManifest(maker, &man) catch |err| switch (err) { | |
| 170 | error.Canceled => |e| return e, | |
| 171 | else => |e| try step.addError(maker, "failed writing cache manifest: {t}", .{e}), | |
| 172 | }; | |
| 173 | } |
lib/compiler/Maker/Step/Options.zig created+104| ... | ... | @@ -0,0 +1,104 @@ |
| 1 | const Options = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Configuration = std.Build.Configuration; | |
| 6 | const Cache = std.Build.Cache; | |
| 7 | ||
| 8 | const Step = @import("../Step.zig"); | |
| 9 | const Maker = @import("../../Maker.zig"); | |
| 10 | ||
| 11 | pub fn make( | |
| 12 | options: *Options, | |
| 13 | step_index: Configuration.Step.Index, | |
| 14 | maker: *Maker, | |
| 15 | progress_node: std.Progress.Node, | |
| 16 | ) Step.ExtendedMakeError!void { | |
| 17 | _ = options; | |
| 18 | ||
| 19 | // This step completes so quickly that no progress reporting is necessary. | |
| 20 | _ = progress_node; | |
| 21 | ||
| 22 | const graph = maker.graph; | |
| 23 | const step = maker.stepByIndex(step_index); | |
| 24 | const io = graph.io; | |
| 25 | const cache_root = graph.local_cache_root; | |
| 26 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 27 | const conf = &maker.scanned_config.configuration; | |
| 28 | const conf_step = step_index.ptr(conf); | |
| 29 | const conf_options = conf_step.extended.get(conf.extra).options; | |
| 30 | const contents = conf_options.contents.slice(conf); | |
| 31 | ||
| 32 | // This step operates under the assumption that all contents of the | |
| 33 | // generated zig file are observable by dependant steps, as well as the | |
| 34 | // contents of files added via Options.Arg. | |
| 35 | ||
| 36 | step.clearWatchInputs(maker); | |
| 37 | ||
| 38 | var man = graph.cache.obtain(); | |
| 39 | defer man.deinit(); | |
| 40 | ||
| 41 | var args_bytes: std.ArrayList(u8) = .empty; | |
| 42 | ||
| 43 | for (conf_options.args.slice) |arg| { | |
| 44 | const name = arg.name.slice(conf); | |
| 45 | const lazy_path = arg.path.get(conf); | |
| 46 | try step.addWatchInput(maker, arena, lazy_path); | |
| 47 | const arg_path = try maker.resolveLazyPath(arena, lazy_path, step_index); | |
| 48 | _ = try man.addFilePath(arg_path, null); | |
| 49 | try args_bytes.print(arena, "pub const {f}: []const u8 = \"{f}\";\n", .{ | |
| 50 | std.zig.fmtId(name), arg_path.fmtEscapeString(), | |
| 51 | }); | |
| 52 | } | |
| 53 | ||
| 54 | man.hash.addBytes(contents); | |
| 55 | man.hash.addBytes(args_bytes.items); | |
| 56 | ||
| 57 | const basename = "options.zig"; | |
| 58 | ||
| 59 | if (try step.cacheHitAndWatch(maker, &man)) { | |
| 60 | const digest = man.final(); | |
| 61 | maker.generatedPath(conf_options.generated_file).* = .{ | |
| 62 | .root_dir = cache_root, | |
| 63 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), | |
| 64 | }; | |
| 65 | step.result_cached = true; | |
| 66 | return; | |
| 67 | } | |
| 68 | ||
| 69 | const digest = man.final(); | |
| 70 | const out_path: Cache.Path = .{ | |
| 71 | .root_dir = cache_root, | |
| 72 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest, basename }), | |
| 73 | }; | |
| 74 | ||
| 75 | var file: Io.File = out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |err| switch (err) { | |
| 76 | error.Canceled => |e| return e, | |
| 77 | error.FileNotFound => f: { | |
| 78 | out_path.root_dir.handle.createDirPath(io, Io.Dir.path.dirname(out_path.sub_path).?) catch |inner| switch (inner) { | |
| 79 | error.Canceled => |e| return e, | |
| 80 | else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }), | |
| 81 | }; | |
| 82 | break :f out_path.root_dir.handle.createFile(io, out_path.sub_path, .{}) catch |inner| switch (inner) { | |
| 83 | error.Canceled => |e| return e, | |
| 84 | else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }), | |
| 85 | }; | |
| 86 | }, | |
| 87 | else => |e| return step.fail(maker, "failed to create {f}: {t}", .{ out_path, e }), | |
| 88 | }; | |
| 89 | defer file.close(io); | |
| 90 | ||
| 91 | // No buffer because we already have all contents buffered. | |
| 92 | var file_writer = file.writer(io, &.{}); | |
| 93 | var data: [2][]const u8 = .{ contents, args_bytes.items }; | |
| 94 | file_writer.interface.writeVecAll(&data) catch |write_err| switch (write_err) { | |
| 95 | error.WriteFailed => switch (file_writer.err.?) { | |
| 96 | error.Canceled => |e| return e, | |
| 97 | else => |e| return step.fail(maker, "failed to write to {f}: {t}", .{ out_path, e }), | |
| 98 | }, | |
| 99 | }; | |
| 100 | ||
| 101 | try step.writeManifestAndWatch(maker, &man); | |
| 102 | ||
| 103 | maker.generatedPath(conf_options.generated_file).* = out_path; | |
| 104 | } |
lib/compiler/Maker/Step/Run.zig created+2372| ... | ... | @@ -0,0 +1,2372 @@ |
| 1 | const Run = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | ||
| 5 | const std = @import("std"); | |
| 6 | const Cache = std.Build.Cache; | |
| 7 | const Configuration = std.Build.Configuration; | |
| 8 | const Dir = std.Io.Dir; | |
| 9 | const EnvMap = std.process.Environ.Map; | |
| 10 | const Io = std.Io; | |
| 11 | const Path = std.Build.Cache.Path; | |
| 12 | const assert = std.debug.assert; | |
| 13 | const mem = std.mem; | |
| 14 | const process = std.process; | |
| 15 | const allocPrint = std.fmt.allocPrint; | |
| 16 | const Allocator = std.mem.Allocator; | |
| 17 | ||
| 18 | const Step = @import("../Step.zig"); | |
| 19 | const Maker = @import("../../Maker.zig"); | |
| 20 | const Fuzz = @import("../../Maker/Fuzz.zig"); | |
| 21 | ||
| 22 | /// If this is a Zig unit test binary, this tracks the names of the unit | |
| 23 | /// tests that are also fuzz tests. Indexes cannot be used as they may | |
| 24 | /// change between reruns. | |
| 25 | fuzz_tests: std.ArrayList([]const u8) = .empty, | |
| 26 | cached_test_metadata: ?CachedTestMetadata = null, | |
| 27 | ||
| 28 | /// Populated during the fuzz phase if this run step corresponds to a unit test | |
| 29 | /// executable that contains fuzz tests. | |
| 30 | rebuilt_executable: ?Path = null, | |
| 31 | ||
| 32 | pub fn make( | |
| 33 | run: *Run, | |
| 34 | run_index: Configuration.Step.Index, | |
| 35 | maker: *Maker, | |
| 36 | progress_node: std.Progress.Node, | |
| 37 | ) Step.ExtendedMakeError!void { | |
| 38 | const graph = maker.graph; | |
| 39 | const gpa = maker.gpa; | |
| 40 | const step = maker.stepByIndex(run_index); | |
| 41 | const io = graph.io; | |
| 42 | const conf = &maker.scanned_config.configuration; | |
| 43 | const conf_step = run_index.ptr(conf); | |
| 44 | const conf_run = conf_step.extended.get(conf.extra).run; | |
| 45 | const cache_root = graph.local_cache_root; | |
| 46 | ||
| 47 | var arena_allocator: std.heap.ArenaAllocator = .init(gpa); | |
| 48 | defer arena_allocator.deinit(); | |
| 49 | const arena = arena_allocator.allocator(); | |
| 50 | ||
| 51 | var argv_list: std.ArrayList([]const u8) = .empty; | |
| 52 | defer argv_list.deinit(gpa); | |
| 53 | ||
| 54 | var output_placeholders: std.ArrayList(IndexedOutput) = .empty; | |
| 55 | defer output_placeholders.deinit(gpa); | |
| 56 | ||
| 57 | var man = graph.cache.obtain(); | |
| 58 | defer man.deinit(); | |
| 59 | ||
| 60 | if (conf_run.environ_map.value) |environ_map_index| { | |
| 61 | const environ_map = environ_map_index.get(conf); | |
| 62 | for (environ_map.keys.slice(conf), environ_map.values.slice(conf)) |key, value| { | |
| 63 | man.hash.addBytesZ(key.slice(conf)); | |
| 64 | man.hash.addBytesZ(value.slice(conf)); | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | man.hash.add(graph.fuzzing); | |
| 69 | man.hash.add(conf_run.flags.color); | |
| 70 | man.hash.add(conf_run.flags.disable_zig_progress); | |
| 71 | ||
| 72 | var any_dep_files = false; | |
| 73 | var any_output_args = false; | |
| 74 | var any_cli_positionals = false; | |
| 75 | ||
| 76 | for (conf_run.args.slice) |arg_index| { | |
| 77 | const arg = arg_index.get(conf); | |
| 78 | try argv_list.ensureUnusedCapacity(gpa, 1); | |
| 79 | switch (arg.flags.tag) { | |
| 80 | .string => { | |
| 81 | const prefix = arg.prefix.value.?.slice(conf); | |
| 82 | argv_list.appendAssumeCapacity(prefix); | |
| 83 | man.hash.addBytesZ(prefix); | |
| 84 | }, | |
| 85 | .path_file => { | |
| 86 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 87 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 88 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); | |
| 89 | argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ | |
| 90 | prefix, try convertPathArg(arena, run_index, maker, file_path), suffix, | |
| 91 | })); | |
| 92 | man.hash.addBytesZ(prefix); | |
| 93 | man.hash.addBytesZ(suffix); | |
| 94 | _ = try man.addFilePath(file_path, null); | |
| 95 | }, | |
| 96 | .path_directory => { | |
| 97 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 98 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 99 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); | |
| 100 | const resolved_arg = try mem.concat(arena, u8, &.{ | |
| 101 | prefix, try convertPathArg(arena, run_index, maker, file_path), suffix, | |
| 102 | }); | |
| 103 | argv_list.appendAssumeCapacity(resolved_arg); | |
| 104 | man.hash.addBytes(resolved_arg); | |
| 105 | }, | |
| 106 | .file_content => { | |
| 107 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 108 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 109 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); | |
| 110 | ||
| 111 | var result: std.Io.Writer.Allocating = .init(arena); | |
| 112 | result.writer.writeAll(prefix) catch return error.OutOfMemory; | |
| 113 | ||
| 114 | const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| | |
| 115 | return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err }); | |
| 116 | defer file.close(io); | |
| 117 | ||
| 118 | var file_reader = file.reader(io, &.{}); | |
| 119 | _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { | |
| 120 | error.ReadFailed => switch (file_reader.err.?) { | |
| 121 | error.Canceled => |e| return e, | |
| 122 | else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }), | |
| 123 | }, | |
| 124 | error.WriteFailed => return error.OutOfMemory, | |
| 125 | }; | |
| 126 | result.writer.writeAll(suffix) catch return error.OutOfMemory; | |
| 127 | ||
| 128 | argv_list.appendAssumeCapacity(result.written()); | |
| 129 | man.hash.addBytesZ(prefix); | |
| 130 | man.hash.addBytesZ(suffix); | |
| 131 | _ = try man.addFilePath(file_path, null); | |
| 132 | }, | |
| 133 | .artifact => { | |
| 134 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 135 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 136 | const producer_index = arg.producer.value.?; | |
| 137 | const producer_step = producer_index.ptr(conf); | |
| 138 | const producer = producer_step.extended.get(conf.extra).compile; | |
| 139 | const producer_make_comp_step = maker.stepByIndex(producer_index); | |
| 140 | const producer_make_comp = &producer_make_comp_step.extended.compile; | |
| 141 | ||
| 142 | const file_path = producer_make_comp.installed_path orelse maker.generatedPath(producer.generated_bin.value.?).*; | |
| 143 | ||
| 144 | argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ | |
| 145 | prefix, try convertPathArg(arena, run_index, maker, file_path), suffix, | |
| 146 | })); | |
| 147 | ||
| 148 | _ = try man.addFilePath(file_path, null); | |
| 149 | }, | |
| 150 | .output_file, .output_directory => { | |
| 151 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 152 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 153 | const basename = arg.basename.value.?.slice(conf); | |
| 154 | ||
| 155 | man.hash.addBytesZ(prefix); | |
| 156 | man.hash.addBytesZ(basename); | |
| 157 | man.hash.addBytesZ(suffix); | |
| 158 | man.hash.add(arg.flags.dep_file); | |
| 159 | ||
| 160 | any_dep_files = any_dep_files or arg.flags.dep_file; | |
| 161 | any_output_args = true; | |
| 162 | ||
| 163 | // Add a placeholder into the argument list because we need the | |
| 164 | // manifest hash to be updated with all arguments before the | |
| 165 | // object directory is computed. | |
| 166 | try output_placeholders.append(gpa, .{ | |
| 167 | .index = @intCast(argv_list.items.len), | |
| 168 | .arg_index = arg_index, | |
| 169 | }); | |
| 170 | argv_list.items.len += 1; | |
| 171 | }, | |
| 172 | .passthru => { | |
| 173 | any_cli_positionals = true; | |
| 174 | if (maker.run_args) |run_args| { | |
| 175 | try argv_list.appendSlice(gpa, run_args); | |
| 176 | man.hash.addListOfBytes(run_args); | |
| 177 | } | |
| 178 | }, | |
| 179 | } | |
| 180 | } | |
| 181 | ||
| 182 | man.hash.add(conf_run.flags.test_runner_mode); | |
| 183 | if (conf_run.flags.test_runner_mode) { | |
| 184 | const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }); | |
| 185 | ||
| 186 | try argv_list.ensureUnusedCapacity(gpa, 3); | |
| 187 | argv_list.appendAssumeCapacity(try allocPrint(arena, "--cache-dir={s}", .{cache_dir_string})); | |
| 188 | argv_list.appendAssumeCapacity(try allocPrint(arena, "--seed=0x{x}", .{graph.random_seed})); | |
| 189 | argv_list.appendAssumeCapacity("--listen=-"); | |
| 190 | } | |
| 191 | ||
| 192 | switch (conf_run.stdin.u) { | |
| 193 | .bytes => |bytes| { | |
| 194 | man.hash.addBytes(bytes.slice(conf)); | |
| 195 | }, | |
| 196 | .lazy_path => |lazy_path| { | |
| 197 | const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); | |
| 198 | _ = try man.addFilePath(file_path, null); | |
| 199 | }, | |
| 200 | .none => {}, | |
| 201 | } | |
| 202 | ||
| 203 | if (conf_run.captured_stdout.value) |captured| { | |
| 204 | man.hash.addBytes(captured.basename.slice(conf)); | |
| 205 | man.hash.add(conf_run.flags.stdout_trim_whitespace); | |
| 206 | } | |
| 207 | ||
| 208 | if (conf_run.captured_stderr.value) |captured| { | |
| 209 | man.hash.addBytes(captured.basename.slice(conf)); | |
| 210 | man.hash.add(conf_run.flags.stderr_trim_whitespace); | |
| 211 | } | |
| 212 | ||
| 213 | switch (conf_run.flags.stdio) { | |
| 214 | .infer_from_args, .inherit, .zig_test => {}, | |
| 215 | .check => { | |
| 216 | man.hash.addBytes(if (conf_run.expect_stderr_exact.value) |bytes| bytes.slice(conf) else ""); | |
| 217 | man.hash.addBytes(if (conf_run.expect_stdout_exact.value) |bytes| bytes.slice(conf) else ""); | |
| 218 | for (conf_run.expect_stderr_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf)); | |
| 219 | for (conf_run.expect_stdout_match.slice) |bytes| man.hash.addBytes(bytes.slice(conf)); | |
| 220 | man.hash.add(conf_run.flags2.expect_term_status); | |
| 221 | man.hash.addOptional(conf_run.expect_term_value.value); | |
| 222 | }, | |
| 223 | } | |
| 224 | ||
| 225 | for (conf_run.file_inputs.slice) |lazy_path| { | |
| 226 | const file_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); | |
| 227 | _ = try man.addFilePath(file_path, null); | |
| 228 | } | |
| 229 | ||
| 230 | if (conf_run.cwd.value) |lazy_path| { | |
| 231 | const cwd_path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); | |
| 232 | _ = man.hash.addBytes(try cwd_path.toString(arena)); | |
| 233 | } | |
| 234 | ||
| 235 | // Whether the Run step has side effects *other than* updating the output arguments. | |
| 236 | // When fuzzing we need to always run the test runner to populate fuzz_tests. | |
| 237 | const has_side_effects = graph.fuzzing or conf_run.flags.has_side_effects or any_cli_positionals or | |
| 238 | switch (conf_run.flags.stdio) { | |
| 239 | .infer_from_args => !any_output_args and | |
| 240 | conf_run.captured_stdout.value == null and | |
| 241 | conf_run.captured_stderr.value == null, | |
| 242 | .inherit => true, | |
| 243 | .check, .zig_test => false, | |
| 244 | }; | |
| 245 | ||
| 246 | if (!has_side_effects and try step.cacheHitAndWatch(maker, &man)) { | |
| 247 | // Cache hit; skip running command. | |
| 248 | const digest = man.final(); | |
| 249 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); | |
| 250 | try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest); | |
| 251 | step.result_cached = true; | |
| 252 | return; | |
| 253 | } | |
| 254 | ||
| 255 | if (!any_dep_files) { | |
| 256 | // We already know the final output paths; use them directly. | |
| 257 | const digest = if (has_side_effects) man.hash.final() else man.final(); | |
| 258 | const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; | |
| 259 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); | |
| 260 | try populateGeneratedPathsCreateDirs(arena, run_index, maker, output_dir_path, output_placeholders.items, argv_list.items); | |
| 261 | try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null); | |
| 262 | if (!has_side_effects) try step.writeManifestAndWatch(maker, &man); | |
| 263 | return; | |
| 264 | } | |
| 265 | ||
| 266 | // We do not know the final output paths yet; use temporary directory to run the command. | |
| 267 | var rand_int: u64 = undefined; | |
| 268 | io.random(@ptrCast(&rand_int)); | |
| 269 | const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); | |
| 270 | ||
| 271 | try populateGeneratedPathsCreateDirs(arena, run_index, maker, tmp_dir_path, output_placeholders.items, argv_list.items); | |
| 272 | try runCommand(arena, run, run_index, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null); | |
| 273 | ||
| 274 | for (output_placeholders.items) |placeholder| { | |
| 275 | const arg = placeholder.arg_index.get(conf); | |
| 276 | switch (arg.flags.tag) { | |
| 277 | .output_file => if (arg.flags.dep_file) { | |
| 278 | const generated_path = maker.generatedPath(arg.generated.value.?).*; | |
| 279 | const result = if (has_side_effects) | |
| 280 | man.addDepFile(generated_path.root_dir.handle, generated_path.sub_path) | |
| 281 | else | |
| 282 | man.addDepFilePost(generated_path.root_dir.handle, generated_path.sub_path); | |
| 283 | result catch |err| switch (err) { | |
| 284 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 285 | else => |e| return step.fail(maker, "failed adding to cache the file {f}: {t}", .{ | |
| 286 | generated_path, e, | |
| 287 | }), | |
| 288 | }; | |
| 289 | }, | |
| 290 | .output_directory => continue, | |
| 291 | else => unreachable, | |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | const digest = if (has_side_effects) man.hash.final() else man.final(); | |
| 296 | ||
| 297 | const any_output = output_placeholders.items.len > 0 or | |
| 298 | conf_run.captured_stdout.value != null or conf_run.captured_stderr.value != null; | |
| 299 | ||
| 300 | if (any_output) { | |
| 301 | // Rename into place. | |
| 302 | const tmp_path: Path = .{ .root_dir = cache_root, .sub_path = tmp_dir_path }; | |
| 303 | const dst_path: Path = .{ .root_dir = cache_root, .sub_path = "o" ++ Dir.path.sep_str ++ &digest }; | |
| 304 | Dir.rename( | |
| 305 | tmp_path.root_dir.handle, | |
| 306 | tmp_path.sub_path, | |
| 307 | dst_path.root_dir.handle, | |
| 308 | dst_path.sub_path, | |
| 309 | io, | |
| 310 | ) catch |err| switch (err) { | |
| 311 | error.DirNotEmpty => { | |
| 312 | dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err| | |
| 313 | return step.fail(maker, "failed to remove tree {f}: {t}", .{ dst_path, del_err }); | |
| 314 | ||
| 315 | Dir.rename( | |
| 316 | tmp_path.root_dir.handle, | |
| 317 | tmp_path.sub_path, | |
| 318 | dst_path.root_dir.handle, | |
| 319 | dst_path.sub_path, | |
| 320 | io, | |
| 321 | ) catch |retry_err| return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ | |
| 322 | tmp_path, dst_path, retry_err, | |
| 323 | }); | |
| 324 | }, | |
| 325 | else => return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ | |
| 326 | tmp_path, dst_path, err, | |
| 327 | }), | |
| 328 | }; | |
| 329 | } | |
| 330 | ||
| 331 | if (!has_side_effects) try step.writeManifestAndWatch(maker, &man); | |
| 332 | ||
| 333 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); | |
| 334 | try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest); | |
| 335 | } | |
| 336 | ||
| 337 | /// Reads stdout of a Zig test process until a termination condition is reached: | |
| 338 | /// * A write fails, indicating the child unexpectedly closed stdin | |
| 339 | /// * A test (or a response from the test runner) times out | |
| 340 | /// * The wait fails, indicating the child closed stdout and stderr | |
| 341 | fn waitZigTest( | |
| 342 | arena: Allocator, | |
| 343 | run: *Run, | |
| 344 | run_index: Configuration.Step.Index, | |
| 345 | maker: *Maker, | |
| 346 | child: *process.Child, | |
| 347 | progress_node: std.Progress.Node, | |
| 348 | multi_reader: *Io.File.MultiReader, | |
| 349 | opt_metadata: *?TestMetadata, | |
| 350 | results: *Step.TestResults, | |
| 351 | ) !union(enum) { | |
| 352 | write_failed: anyerror, | |
| 353 | no_poll: struct { | |
| 354 | active_test_index: ?u32, | |
| 355 | ns_elapsed: u64, | |
| 356 | }, | |
| 357 | timeout: struct { | |
| 358 | active_test_index: ?u32, | |
| 359 | ns_elapsed: u64, | |
| 360 | }, | |
| 361 | } { | |
| 362 | const graph = maker.graph; | |
| 363 | const gpa = maker.gpa; | |
| 364 | const io = graph.io; | |
| 365 | const step = maker.stepByIndex(run_index); | |
| 366 | ||
| 367 | var sub_prog_node: ?std.Progress.Node = null; | |
| 368 | defer if (sub_prog_node) |n| n.end(); | |
| 369 | ||
| 370 | if (opt_metadata.*) |*md| { | |
| 371 | // Previous unit test process died or was killed; we're continuing where it left off | |
| 372 | requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 373 | } else { | |
| 374 | // Running unit tests normally | |
| 375 | run.fuzz_tests.clearRetainingCapacity(); | |
| 376 | sendMessage(io, child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err }; | |
| 377 | } | |
| 378 | ||
| 379 | var active_test_index: ?u32 = null; | |
| 380 | ||
| 381 | var last_update: Io.Clock.Timestamp = .now(io, .awake); | |
| 382 | ||
| 383 | // This timeout is used when we're waiting on the test runner itself rather than a user-specified | |
| 384 | // test. For instance, if the test runner leaves this much time between us requesting a test to | |
| 385 | // start and it acknowledging the test starting, we terminate the child and raise an error. This | |
| 386 | // *should* never happen, but could in theory be caused by some very unlucky IB in a test. | |
| 387 | const response_timeout: Io.Clock.Duration = t: { | |
| 388 | const ns = @max(maker.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); | |
| 389 | break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) }; | |
| 390 | }; | |
| 391 | const test_timeout: ?Io.Clock.Duration = if (maker.unit_test_timeout_ns) |ns| .{ | |
| 392 | .clock = .awake, | |
| 393 | .raw = .fromNanoseconds(ns), | |
| 394 | } else null; | |
| 395 | ||
| 396 | const stdout = multi_reader.reader(0); | |
| 397 | const stderr = multi_reader.reader(1); | |
| 398 | const Header = std.zig.Server.Message.Header; | |
| 399 | ||
| 400 | while (true) { | |
| 401 | const timeout: Io.Timeout = t: { | |
| 402 | const opt_duration = if (active_test_index == null) response_timeout else test_timeout; | |
| 403 | const duration = opt_duration orelse break :t .none; | |
| 404 | break :t .{ .deadline = last_update.addDuration(duration) }; | |
| 405 | }; | |
| 406 | ||
| 407 | // This block is exited when `stdout` contains enough bytes for a `Header`. | |
| 408 | header_ready: { | |
| 409 | if (stdout.buffered().len >= @sizeOf(Header)) { | |
| 410 | // We already have one, no need to poll! | |
| 411 | break :header_ready; | |
| 412 | } | |
| 413 | ||
| 414 | multi_reader.fill(64, timeout) catch |err| switch (err) { | |
| 415 | error.Timeout => return .{ .timeout = .{ | |
| 416 | .active_test_index = active_test_index, | |
| 417 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 418 | } }, | |
| 419 | error.EndOfStream => return .{ .no_poll = .{ | |
| 420 | .active_test_index = active_test_index, | |
| 421 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 422 | } }, | |
| 423 | else => |e| return e, | |
| 424 | }; | |
| 425 | ||
| 426 | continue; | |
| 427 | } | |
| 428 | // There is definitely a header available now -- read it. | |
| 429 | const header = stdout.takeStruct(Header, .little) catch unreachable; | |
| 430 | ||
| 431 | while (stdout.buffered().len < header.bytes_len) { | |
| 432 | multi_reader.fill(64, timeout) catch |err| switch (err) { | |
| 433 | error.Timeout => return .{ .timeout = .{ | |
| 434 | .active_test_index = active_test_index, | |
| 435 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 436 | } }, | |
| 437 | error.EndOfStream => return .{ .no_poll = .{ | |
| 438 | .active_test_index = active_test_index, | |
| 439 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 440 | } }, | |
| 441 | else => |e| return e, | |
| 442 | }; | |
| 443 | } | |
| 444 | ||
| 445 | const body = stdout.take(header.bytes_len) catch unreachable; | |
| 446 | var body_r: std.Io.Reader = .fixed(body); | |
| 447 | switch (header.tag) { | |
| 448 | .zig_version => { | |
| 449 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( | |
| 450 | maker, | |
| 451 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 452 | .{ builtin.zig_version_string, body }, | |
| 453 | ); | |
| 454 | }, | |
| 455 | .test_metadata => { | |
| 456 | // `metadata` would only be populated if we'd already seen a `test_metadata`, but we | |
| 457 | // only request it once (and importantly, we don't re-request it if we kill and | |
| 458 | // restart the test runner). | |
| 459 | assert(opt_metadata.* == null); | |
| 460 | ||
| 461 | const tm_hdr = body_r.takeStruct(std.zig.Server.Message.TestMetadata, .little) catch unreachable; | |
| 462 | results.test_count = tm_hdr.tests_len; | |
| 463 | ||
| 464 | const names = try arena.alloc(u32, results.test_count); | |
| 465 | for (names) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable; | |
| 466 | ||
| 467 | const expected_panic_msgs = try arena.alloc(u32, results.test_count); | |
| 468 | for (expected_panic_msgs) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable; | |
| 469 | ||
| 470 | const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable; | |
| 471 | ||
| 472 | progress_node.setEstimatedTotalItems(names.len); | |
| 473 | opt_metadata.* = .{ | |
| 474 | .string_bytes = try arena.dupe(u8, string_bytes), | |
| 475 | .ns_per_test = try arena.alloc(u64, results.test_count), | |
| 476 | .names = names, | |
| 477 | .expected_panic_msgs = expected_panic_msgs, | |
| 478 | .next_index = 0, | |
| 479 | .prog_node = progress_node, | |
| 480 | }; | |
| 481 | @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); | |
| 482 | ||
| 483 | active_test_index = null; | |
| 484 | last_update = .now(io, .awake); | |
| 485 | ||
| 486 | requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 487 | }, | |
| 488 | .test_started => { | |
| 489 | active_test_index = opt_metadata.*.?.next_index - 1; | |
| 490 | last_update = .now(io, .awake); | |
| 491 | }, | |
| 492 | .test_results => { | |
| 493 | const md = &opt_metadata.*.?; | |
| 494 | ||
| 495 | const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable; | |
| 496 | assert(tr_hdr.index == active_test_index); | |
| 497 | ||
| 498 | switch (tr_hdr.flags.status) { | |
| 499 | .pass => {}, | |
| 500 | .skip => results.skip_count +|= 1, | |
| 501 | .fail => results.fail_count +|= 1, | |
| 502 | } | |
| 503 | const leak_count = tr_hdr.flags.leak_count; | |
| 504 | const log_err_count = tr_hdr.flags.log_err_count; | |
| 505 | results.leak_count +|= leak_count; | |
| 506 | results.log_err_count +|= log_err_count; | |
| 507 | ||
| 508 | if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, md.testName(tr_hdr.index)); | |
| 509 | ||
| 510 | if (tr_hdr.flags.status == .fail) { | |
| 511 | const name = md.testName(tr_hdr.index); | |
| 512 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); | |
| 513 | stderr.tossBuffered(); | |
| 514 | if (stderr_bytes.len == 0) { | |
| 515 | try step.addError(maker, "'{s}' failed without output", .{name}); | |
| 516 | } else { | |
| 517 | try step.addError(maker, "'{s}' failed:\n{s}", .{ name, stderr_bytes }); | |
| 518 | } | |
| 519 | } else if (leak_count > 0) { | |
| 520 | const name = md.testName(tr_hdr.index); | |
| 521 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); | |
| 522 | stderr.tossBuffered(); | |
| 523 | try step.addError(maker, "'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes }); | |
| 524 | } else if (log_err_count > 0) { | |
| 525 | const name = md.testName(tr_hdr.index); | |
| 526 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); | |
| 527 | stderr.tossBuffered(); | |
| 528 | try step.addError(maker, "'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes }); | |
| 529 | } | |
| 530 | ||
| 531 | active_test_index = null; | |
| 532 | ||
| 533 | const now: Io.Clock.Timestamp = .now(io, .awake); | |
| 534 | md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds); | |
| 535 | last_update = now; | |
| 536 | ||
| 537 | requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 538 | }, | |
| 539 | else => {}, // ignore other messages | |
| 540 | } | |
| 541 | } | |
| 542 | } | |
| 543 | ||
| 544 | const FuzzTestRunner = struct { | |
| 545 | run: *Run, | |
| 546 | run_index: Configuration.Step.Index, | |
| 547 | ctx: FuzzContext, | |
| 548 | coverage_id: ?u64, | |
| 549 | ||
| 550 | instances: []Instance, | |
| 551 | /// The indexes of this are layed out such that it is effectively an array | |
| 552 | /// of `[instances.len][3]Io.Operation.Storage` of stdin, stdout, stderr. | |
| 553 | batch: Io.Batch, | |
| 554 | /// LIFO. Stream of message bodies trailed by PendingBroadcastFooter. | |
| 555 | pending_broadcasts: std.ArrayList(u8), | |
| 556 | broadcast: std.ArrayList(u8), | |
| 557 | broadcast_undelivered: u32, | |
| 558 | ||
| 559 | const Instance = struct { | |
| 560 | child: process.Child, | |
| 561 | message: std.ArrayListAligned(u8, .@"4"), | |
| 562 | broadcast_written: usize, | |
| 563 | stderr: std.ArrayList(u8), | |
| 564 | stdin_vec: [1][]u8, | |
| 565 | stdout_vec: [1][]u8, | |
| 566 | stderr_vec: [1][]u8, | |
| 567 | progress_node: std.Progress.Node, | |
| 568 | ||
| 569 | fn messageHeader(instance: *Instance) InHeader { | |
| 570 | assert(instance.message.items.len >= @sizeOf(InHeader)); | |
| 571 | const header_ptr: *InHeader = @ptrCast(instance.message.items); | |
| 572 | var header = header_ptr.*; | |
| 573 | if (std.builtin.Endian.native != .little) { | |
| 574 | std.mem.byteSwapAllFields(InHeader, &header); | |
| 575 | } | |
| 576 | return header; | |
| 577 | } | |
| 578 | }; | |
| 579 | ||
| 580 | const PendingBroadcastFooter = struct { | |
| 581 | from_id: u32, | |
| 582 | body_len: u32, | |
| 583 | }; | |
| 584 | ||
| 585 | const InHeader = std.zig.Server.Message.Header; | |
| 586 | const OutHeader = std.zig.Client.Message.Header; | |
| 587 | ||
| 588 | const stdin_i = 0; | |
| 589 | const stdout_i = 1; | |
| 590 | const stderr_i = 2; | |
| 591 | ||
| 592 | fn init( | |
| 593 | run: *Run, | |
| 594 | run_index: Configuration.Step.Index, | |
| 595 | ctx: FuzzContext, | |
| 596 | progress_node: std.Progress.Node, | |
| 597 | spawn_options: process.SpawnOptions, | |
| 598 | ) !FuzzTestRunner { | |
| 599 | const maker = ctx.fuzz.maker; | |
| 600 | const graph = maker.graph; | |
| 601 | const gpa = maker.gpa; | |
| 602 | const io = graph.io; | |
| 603 | ||
| 604 | const n_instances = switch (ctx.fuzz.mode) { | |
| 605 | .forever => graph.max_jobs orelse @min( | |
| 606 | std.Thread.getCpuCount() catch 1, | |
| 607 | (std.math.maxInt(u32) - 2) / 3, | |
| 608 | ), | |
| 609 | .limit => 1, | |
| 610 | }; | |
| 611 | const instances = try gpa.alloc(Instance, n_instances); | |
| 612 | errdefer gpa.free(instances); | |
| 613 | const batch_storage = try gpa.alloc(Io.Operation.Storage, instances.len * 3); | |
| 614 | errdefer gpa.free(batch_storage); | |
| 615 | ||
| 616 | @memset(instances, .{ | |
| 617 | .child = undefined, | |
| 618 | .message = .empty, | |
| 619 | .broadcast_written = undefined, | |
| 620 | .stderr = .empty, | |
| 621 | .stdin_vec = undefined, | |
| 622 | .stdout_vec = undefined, | |
| 623 | .stderr_vec = undefined, | |
| 624 | .progress_node = undefined, | |
| 625 | }); | |
| 626 | for (0.., instances) |id, *instance| { | |
| 627 | errdefer for (instances[0..id]) |*spawned| { | |
| 628 | spawned.child.kill(io); | |
| 629 | spawned.progress_node.end(); | |
| 630 | }; | |
| 631 | instance.child = try process.spawn(io, spawn_options); | |
| 632 | instance.progress_node = progress_node.start("starting fuzzer", 0); | |
| 633 | } | |
| 634 | ||
| 635 | return .{ | |
| 636 | .run = run, | |
| 637 | .run_index = run_index, | |
| 638 | .ctx = ctx, | |
| 639 | .coverage_id = null, | |
| 640 | ||
| 641 | .instances = instances, | |
| 642 | .batch = .init(batch_storage), | |
| 643 | .pending_broadcasts = .empty, | |
| 644 | .broadcast = .empty, | |
| 645 | .broadcast_undelivered = 0, | |
| 646 | }; | |
| 647 | } | |
| 648 | ||
| 649 | fn deinit(f: *FuzzTestRunner) void { | |
| 650 | const maker = f.ctx.fuzz.maker; | |
| 651 | const run_index = f.run_index; | |
| 652 | ||
| 653 | const graph = maker.graph; | |
| 654 | const gpa = maker.gpa; | |
| 655 | const io = graph.io; | |
| 656 | const step = maker.stepByIndex(run_index); | |
| 657 | ||
| 658 | f.batch.cancel(io); | |
| 659 | gpa.free(f.batch.storage); | |
| 660 | var total_rss: usize = 0; | |
| 661 | for (f.instances) |*instance| { | |
| 662 | instance.child.kill(io); | |
| 663 | instance.message.deinit(gpa); | |
| 664 | instance.stderr.deinit(gpa); | |
| 665 | instance.progress_node.end(); | |
| 666 | total_rss += instance.child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 667 | } | |
| 668 | step.result_peak_rss = @max(step.result_peak_rss, total_rss); | |
| 669 | gpa.free(f.instances); | |
| 670 | } | |
| 671 | ||
| 672 | fn startInstances(f: *FuzzTestRunner) !void { | |
| 673 | const maker = f.ctx.fuzz.maker; | |
| 674 | const run_index = f.run_index; | |
| 675 | const run = f.run; | |
| 676 | ||
| 677 | const graph = maker.graph; | |
| 678 | const io = graph.io; | |
| 679 | const step = maker.stepByIndex(run_index); | |
| 680 | ||
| 681 | for (0.., f.instances) |id, *instance| { | |
| 682 | const id32: u32 = @intCast(id); | |
| 683 | (switch (f.ctx.fuzz.mode) { | |
| 684 | .forever => sendRunFuzzTestMessage( | |
| 685 | io, | |
| 686 | instance.child.stdin.?, | |
| 687 | run.fuzz_tests.items, | |
| 688 | .forever, | |
| 689 | id32, | |
| 690 | ), | |
| 691 | .limit => |limit| sendRunFuzzTestMessage( | |
| 692 | io, | |
| 693 | instance.child.stdin.?, | |
| 694 | run.fuzz_tests.items, | |
| 695 | .iterations, | |
| 696 | limit.amount, | |
| 697 | ), | |
| 698 | }) catch |write_err| { | |
| 699 | // The runner unexpectedly closed stdin, which means it crashed during initialization. | |
| 700 | // Clean up everything and wait for the child to exit. | |
| 701 | instance.child.stdin.?.close(io); | |
| 702 | instance.child.stdin = null; | |
| 703 | const term = try instance.child.wait(io); | |
| 704 | return step.fail( | |
| 705 | maker, | |
| 706 | "unable to write stdin ({t}); test process unexpectedly {f}", | |
| 707 | .{ write_err, fmtTerm(term) }, | |
| 708 | ); | |
| 709 | }; | |
| 710 | ||
| 711 | try f.addStdoutRead(id32, @sizeOf(InHeader)); | |
| 712 | try f.addStderrRead(id32); | |
| 713 | } | |
| 714 | } | |
| 715 | ||
| 716 | fn listen(f: *FuzzTestRunner, arena: Allocator) !void { | |
| 717 | const maker = f.ctx.fuzz.maker; | |
| 718 | const graph = maker.graph; | |
| 719 | const io = graph.io; | |
| 720 | ||
| 721 | while (true) { | |
| 722 | try f.batch.awaitConcurrent(io, .none); | |
| 723 | while (f.batch.next()) |completion| { | |
| 724 | const id = completion.index / 3; | |
| 725 | const result = completion.result; | |
| 726 | switch (completion.index % 3) { | |
| 727 | 0 => try f.completeStdinWrite(id, result.file_write_streaming catch |e| switch (e) { | |
| 728 | // Avoid calling `instanceEos` until EndOfStream is seen with stderr so | |
| 729 | // that all stderr is collected. | |
| 730 | error.BrokenPipe => continue, | |
| 731 | else => |write_e| return write_e, | |
| 732 | }), | |
| 733 | 1 => try f.completeStdoutRead(id, result.file_read_streaming catch |e| switch (e) { | |
| 734 | // Avoid calling `instanceEos` until EndOfStream is seen with stderr so | |
| 735 | // that all stderr is collected. | |
| 736 | error.EndOfStream => continue, | |
| 737 | else => |read_e| return read_e, | |
| 738 | }), | |
| 739 | 2 => try f.completeStderrRead(id, result.file_read_streaming catch |e| switch (e) { | |
| 740 | error.EndOfStream => return f.instanceEos(arena, id), | |
| 741 | else => |read_e| return read_e, | |
| 742 | }), | |
| 743 | else => unreachable, | |
| 744 | } | |
| 745 | } | |
| 746 | } | |
| 747 | } | |
| 748 | ||
| 749 | fn completeStdoutRead(f: *FuzzTestRunner, id: u32, n: usize) !void { | |
| 750 | const maker = f.ctx.fuzz.maker; | |
| 751 | const instance = &f.instances[id]; | |
| 752 | const run_index = f.run_index; | |
| 753 | const run = f.run; | |
| 754 | ||
| 755 | const graph = maker.graph; | |
| 756 | const gpa = maker.gpa; | |
| 757 | const io = graph.io; | |
| 758 | const step = maker.stepByIndex(run_index); | |
| 759 | ||
| 760 | instance.message.items.len += n; | |
| 761 | const total_read = instance.message.items.len; | |
| 762 | if (total_read < @sizeOf(InHeader)) { | |
| 763 | try f.addStdoutRead(id, @sizeOf(InHeader)); | |
| 764 | return; | |
| 765 | } | |
| 766 | ||
| 767 | const header = instance.messageHeader(); | |
| 768 | const body = instance.message.items[@sizeOf(InHeader)..]; | |
| 769 | if (body.len != header.bytes_len) { | |
| 770 | try f.addStdoutRead(id, @sizeOf(InHeader) + header.bytes_len); | |
| 771 | return; | |
| 772 | } | |
| 773 | ||
| 774 | switch (header.tag) { | |
| 775 | .zig_version => { | |
| 776 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) return step.fail( | |
| 777 | maker, | |
| 778 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 779 | .{ builtin.zig_version_string, body }, | |
| 780 | ); | |
| 781 | }, | |
| 782 | .coverage_id => { | |
| 783 | var body_r: Io.Reader = .fixed(body); | |
| 784 | f.coverage_id = body_r.takeInt(u64, .little) catch unreachable; | |
| 785 | const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable; | |
| 786 | const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable; | |
| 787 | const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable; | |
| 788 | ||
| 789 | const fuzz = f.ctx.fuzz; | |
| 790 | fuzz.queue_mutex.lockUncancelable(io); | |
| 791 | defer fuzz.queue_mutex.unlock(io); | |
| 792 | try fuzz.msg_queue.append(gpa, .{ .coverage = .{ | |
| 793 | .id = f.coverage_id.?, | |
| 794 | .cumulative = .{ | |
| 795 | .runs = cumulative_runs, | |
| 796 | .unique = cumulative_unique, | |
| 797 | .coverage = cumulative_coverage, | |
| 798 | }, | |
| 799 | .run = run_index, | |
| 800 | } }); | |
| 801 | fuzz.queue_cond.signal(io); | |
| 802 | }, | |
| 803 | .fuzz_start_addr => { | |
| 804 | var body_r: Io.Reader = .fixed(body); | |
| 805 | const fuzz = f.ctx.fuzz; | |
| 806 | const addr = body_r.takeInt(u64, .little) catch unreachable; | |
| 807 | ||
| 808 | fuzz.queue_mutex.lockUncancelable(io); | |
| 809 | defer fuzz.queue_mutex.unlock(io); | |
| 810 | try fuzz.msg_queue.append(gpa, .{ .entry_point = .{ | |
| 811 | .addr = addr, | |
| 812 | .coverage_id = f.coverage_id.?, | |
| 813 | } }); | |
| 814 | fuzz.queue_cond.signal(io); | |
| 815 | }, | |
| 816 | .fuzz_test_change => { | |
| 817 | const test_i = std.mem.readInt(u32, body[0..4], .little); | |
| 818 | instance.progress_node.setName(run.fuzz_tests.items[test_i]); | |
| 819 | }, | |
| 820 | .broadcast_fuzz_input => { | |
| 821 | if (f.instances.len == 1) { | |
| 822 | // No other processes to broadcast to. | |
| 823 | } else if (f.broadcast_undelivered == 0) { | |
| 824 | try f.instanceBroadcast(id, body); | |
| 825 | } else { | |
| 826 | const footer: PendingBroadcastFooter = .{ | |
| 827 | .from_id = id, | |
| 828 | .body_len = @intCast(body.len), | |
| 829 | }; | |
| 830 | // There is another broadcast in progress so add this one to the queue. | |
| 831 | const size = @sizeOf(PendingBroadcastFooter) + body.len; | |
| 832 | try f.pending_broadcasts.ensureUnusedCapacity(gpa, size); | |
| 833 | f.pending_broadcasts.appendSliceAssumeCapacity(body); | |
| 834 | f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer)); | |
| 835 | } | |
| 836 | }, | |
| 837 | else => {}, // ignore other messages | |
| 838 | } | |
| 839 | ||
| 840 | instance.message.clearRetainingCapacity(); | |
| 841 | try f.addStdoutRead(id, @sizeOf(InHeader)); | |
| 842 | } | |
| 843 | ||
| 844 | fn completeStderrRead(f: *FuzzTestRunner, id: u32, n: usize) !void { | |
| 845 | const instance = &f.instances[id]; | |
| 846 | instance.stderr.items.len += n; | |
| 847 | try f.addStderrRead(id); | |
| 848 | } | |
| 849 | ||
| 850 | fn completeStdinWrite(f: *FuzzTestRunner, id: u32, n: usize) !void { | |
| 851 | const instance = &f.instances[id]; | |
| 852 | ||
| 853 | instance.broadcast_written += n; | |
| 854 | if (instance.broadcast_written == f.broadcast.items.len) { | |
| 855 | f.broadcast_undelivered -= 1; | |
| 856 | if (f.broadcast_undelivered == 0) { | |
| 857 | try f.broadcastComplete(); | |
| 858 | } | |
| 859 | } else { | |
| 860 | f.addStdinWrite(id); | |
| 861 | } | |
| 862 | } | |
| 863 | ||
| 864 | fn addStdoutRead(f: *FuzzTestRunner, id: u32, end: usize) !void { | |
| 865 | const maker = f.ctx.fuzz.maker; | |
| 866 | const gpa = maker.gpa; | |
| 867 | const instance = &f.instances[id]; | |
| 868 | ||
| 869 | try instance.message.ensureTotalCapacity(gpa, end); | |
| 870 | const start = instance.message.items.len; | |
| 871 | instance.stdout_vec = .{instance.message.allocatedSlice()[start..end]}; | |
| 872 | f.batch.addAt(id * 3 + stdout_i, .{ .file_read_streaming = .{ | |
| 873 | .file = instance.child.stdout.?, | |
| 874 | .data = &instance.stdout_vec, | |
| 875 | } }); | |
| 876 | } | |
| 877 | ||
| 878 | fn addStderrRead(f: *FuzzTestRunner, id: u32) !void { | |
| 879 | const maker = f.ctx.fuzz.maker; | |
| 880 | const gpa = maker.gpa; | |
| 881 | const instance = &f.instances[id]; | |
| 882 | ||
| 883 | try instance.stderr.ensureUnusedCapacity(gpa, 1); | |
| 884 | instance.stderr_vec = .{instance.stderr.unusedCapacitySlice()}; | |
| 885 | f.batch.addAt(id * 3 + stderr_i, .{ .file_read_streaming = .{ | |
| 886 | .file = instance.child.stderr.?, | |
| 887 | .data = &instance.stderr_vec, | |
| 888 | } }); | |
| 889 | } | |
| 890 | ||
| 891 | fn addStdinWrite(f: *FuzzTestRunner, id: u32) void { | |
| 892 | const instance = &f.instances[id]; | |
| 893 | ||
| 894 | assert(f.broadcast.items.len != instance.broadcast_written); | |
| 895 | instance.stdin_vec = .{f.broadcast.items[instance.broadcast_written..]}; | |
| 896 | f.batch.addAt(id * 3 + stdin_i, .{ .file_write_streaming = .{ | |
| 897 | .file = instance.child.stdin.?, | |
| 898 | .data = &instance.stdin_vec, | |
| 899 | } }); | |
| 900 | } | |
| 901 | ||
| 902 | fn instanceEos(f: *FuzzTestRunner, arena: Allocator, id: u32) !void { | |
| 903 | const maker = f.ctx.fuzz.maker; | |
| 904 | const instance = &f.instances[id]; | |
| 905 | const run_index = f.run_index; | |
| 906 | ||
| 907 | const graph = maker.graph; | |
| 908 | const io = graph.io; | |
| 909 | const step = maker.stepByIndex(run_index); | |
| 910 | ||
| 911 | instance.child.stdin.?.close(io); | |
| 912 | instance.child.stdin = null; | |
| 913 | const term = try instance.child.wait(io); | |
| 914 | if (!termMatches(.{ .exited = 0 }, term)) { | |
| 915 | step.result_stderr = try f.mergedStderr(arena); | |
| 916 | try f.saveCrash(id, term); | |
| 917 | return step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)}); | |
| 918 | } | |
| 919 | } | |
| 920 | ||
| 921 | fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void { | |
| 922 | const fuzz = f.ctx.fuzz; | |
| 923 | const run_index = f.run_index; | |
| 924 | const run = f.run; | |
| 925 | ||
| 926 | const maker = fuzz.maker; | |
| 927 | const step = maker.stepByIndex(run_index); | |
| 928 | const graph = maker.graph; | |
| 929 | const io = graph.io; | |
| 930 | const cache_root = graph.local_cache_root; | |
| 931 | ||
| 932 | if (f.coverage_id == null) return; | |
| 933 | ||
| 934 | // Search for the input file corresponding to the instance | |
| 935 | const InputHeader = std.Build.abi.fuzz.MmapInputHeader; | |
| 936 | var in_r_buf: [@sizeOf(InputHeader)]u8 = undefined; | |
| 937 | var in_r: Io.File.Reader = undefined; | |
| 938 | var in_f: Io.File = undefined; | |
| 939 | var in_name_buf: [12]u8 = undefined; | |
| 940 | var in_name: []const u8 = undefined; | |
| 941 | var i: u32 = 0; | |
| 942 | const header: InputHeader = while (true) : ({ | |
| 943 | if (i == std.math.maxInt(u32)) return; | |
| 944 | i += 1; | |
| 945 | }) { | |
| 946 | const name_prefix = "f" ++ Dir.path.sep_str ++ "in"; | |
| 947 | in_name = std.fmt.bufPrint(&in_name_buf, name_prefix ++ "{x}", .{i}) catch unreachable; | |
| 948 | in_f = cache_root.handle.openFile(io, in_name, .{ | |
| 949 | .lock = .exclusive, | |
| 950 | .lock_nonblocking = true, | |
| 951 | }) catch |e| switch (e) { | |
| 952 | error.FileNotFound => return, | |
| 953 | error.WouldBlock => continue, // Can not be from | |
| 954 | // the crashed instance since it is still locked. | |
| 955 | else => return step.fail(maker, "failed to open file '{f}{s}': {t}", .{ | |
| 956 | cache_root, in_name, e, | |
| 957 | }), | |
| 958 | }; | |
| 959 | ||
| 960 | in_r = in_f.readerStreaming(io, &in_r_buf); | |
| 961 | const header = in_r.interface.takeStruct(InputHeader, .little) catch |e| { | |
| 962 | in_f.close(io); | |
| 963 | switch (e) { | |
| 964 | error.ReadFailed => return step.fail(maker, "failed to read file '{f}{s}': {t}", .{ | |
| 965 | cache_root, in_name, in_r.err.?, | |
| 966 | }), | |
| 967 | error.EndOfStream => continue, | |
| 968 | } | |
| 969 | }; | |
| 970 | ||
| 971 | if (header.pc_digest == f.coverage_id.? and | |
| 972 | header.instance_id == id and | |
| 973 | header.test_i < run.fuzz_tests.items.len) | |
| 974 | { | |
| 975 | break header; | |
| 976 | } | |
| 977 | ||
| 978 | in_f.close(io); | |
| 979 | }; | |
| 980 | defer in_f.close(io); | |
| 981 | ||
| 982 | // Save it to a seperate file | |
| 983 | const crash_name = "f" ++ Dir.path.sep_str ++ "crash"; | |
| 984 | const out = cache_root.handle.createFile(io, crash_name, .{ | |
| 985 | .lock = .exclusive, // Multiple run steps could have found a crash at the same time | |
| 986 | }) catch |e| return step.fail(maker, "failed to create file '{f}{s}': {t}", .{ | |
| 987 | cache_root, crash_name, e, | |
| 988 | }); | |
| 989 | defer out.close(io); | |
| 990 | ||
| 991 | var out_w_buf: [512]u8 = undefined; | |
| 992 | var out_w = out.writerStreaming(io, &out_w_buf); | |
| 993 | _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) { | |
| 994 | error.ReadFailed => return step.fail(maker, "failed to read file '{f}{s}': {t}", .{ | |
| 995 | cache_root, in_name, in_r.err.?, | |
| 996 | }), | |
| 997 | error.WriteFailed => return step.fail(maker, "failed to write file '{f}{s}': {t}", .{ | |
| 998 | cache_root, crash_name, out_w.err.?, | |
| 999 | }), | |
| 1000 | }; | |
| 1001 | ||
| 1002 | return step.fail(maker, "test '{s}' {f}; input saved to '{f}{s}'", .{ | |
| 1003 | run.fuzz_tests.items[header.test_i], | |
| 1004 | fmtTerm(term), | |
| 1005 | cache_root, | |
| 1006 | crash_name, | |
| 1007 | }); | |
| 1008 | } | |
| 1009 | ||
| 1010 | fn instanceBroadcast(f: *FuzzTestRunner, from_id: u32, bytes: []const u8) !void { | |
| 1011 | assert(f.instances.len > 1); | |
| 1012 | assert(f.broadcast_undelivered == 0); // no other broadcast is progress | |
| 1013 | assert(f.broadcast.items.len == 0); | |
| 1014 | assert(from_id < f.instances.len); | |
| 1015 | ||
| 1016 | const maker = f.ctx.fuzz.maker; | |
| 1017 | const gpa = maker.gpa; | |
| 1018 | ||
| 1019 | var out_header: OutHeader = .{ | |
| 1020 | .tag = .new_fuzz_input, | |
| 1021 | .bytes_len = @intCast(bytes.len), | |
| 1022 | }; | |
| 1023 | if (std.builtin.Endian.native != .little) { | |
| 1024 | std.mem.byteSwapAllFields(OutHeader, &out_header); | |
| 1025 | } | |
| 1026 | try f.broadcast.ensureTotalCapacity(gpa, @sizeOf(OutHeader) + bytes.len); | |
| 1027 | f.broadcast.appendSliceAssumeCapacity(@ptrCast(&out_header)); | |
| 1028 | f.broadcast.appendSliceAssumeCapacity(bytes); | |
| 1029 | ||
| 1030 | f.broadcast_undelivered = @intCast(f.instances.len - 1); | |
| 1031 | for (0.., f.instances) |to_id, *instance| { | |
| 1032 | if (to_id == from_id) continue; | |
| 1033 | instance.broadcast_written = 0; | |
| 1034 | f.addStdinWrite(@intCast(to_id)); | |
| 1035 | } | |
| 1036 | } | |
| 1037 | ||
| 1038 | fn broadcastComplete(f: *FuzzTestRunner) !void { | |
| 1039 | assert(f.instances.len > 1); | |
| 1040 | assert(f.broadcast_undelivered == 0); | |
| 1041 | f.broadcast.clearRetainingCapacity(); | |
| 1042 | ||
| 1043 | const pending = &f.pending_broadcasts; | |
| 1044 | if (pending.items.len != 0) { | |
| 1045 | // Another broadcast is pending; copy it over to `broadcast` | |
| 1046 | ||
| 1047 | const footer_len = @sizeOf(PendingBroadcastFooter); | |
| 1048 | const footer_bytes = pending.items[pending.items.len - footer_len ..]; | |
| 1049 | const footer: *align(1) PendingBroadcastFooter = @ptrCast(footer_bytes); | |
| 1050 | pending.items.len -= footer_len; | |
| 1051 | ||
| 1052 | const body = pending.items[pending.items.len - footer.body_len ..]; | |
| 1053 | try f.instanceBroadcast(footer.from_id, body); | |
| 1054 | pending.items.len -= body.len; | |
| 1055 | } | |
| 1056 | } | |
| 1057 | ||
| 1058 | fn mergedStderr(f: *FuzzTestRunner, arena: Allocator) Allocator.Error![]const u8 { | |
| 1059 | // Collect any available stderr | |
| 1060 | while (f.batch.next()) |completion| { | |
| 1061 | if (completion.index % 3 != 2) continue; | |
| 1062 | const len = completion.result.file_read_streaming catch continue; | |
| 1063 | f.instances[completion.index / 3].stderr.items.len += len; | |
| 1064 | } | |
| 1065 | ||
| 1066 | var stderr_len: usize = 0; | |
| 1067 | for (f.instances) |*instance| stderr_len += instance.stderr.items.len; | |
| 1068 | const stderr = try arena.alloc(u8, stderr_len); | |
| 1069 | ||
| 1070 | stderr_len = 0; | |
| 1071 | for (f.instances) |*instance| { | |
| 1072 | @memcpy(stderr[stderr_len..][0..instance.stderr.items.len], instance.stderr.items); | |
| 1073 | stderr_len += instance.stderr.items.len; | |
| 1074 | } | |
| 1075 | return stderr; | |
| 1076 | } | |
| 1077 | }; | |
| 1078 | ||
| 1079 | fn evalFuzzTest( | |
| 1080 | run: *Run, | |
| 1081 | run_index: Configuration.Step.Index, | |
| 1082 | progress_node: std.Progress.Node, | |
| 1083 | spawn_options: process.SpawnOptions, | |
| 1084 | fuzz_context: FuzzContext, | |
| 1085 | ) !void { | |
| 1086 | var f: FuzzTestRunner = try .init(run, run_index, fuzz_context, progress_node, spawn_options); | |
| 1087 | defer f.deinit(); | |
| 1088 | try f.startInstances(); | |
| 1089 | try f.listen(fuzz_context.fuzz.maker.graph.arena); | |
| 1090 | } | |
| 1091 | ||
| 1092 | const StdioPollEnum = enum { stdout, stderr }; | |
| 1093 | ||
| 1094 | fn evalZigTest( | |
| 1095 | arena: Allocator, | |
| 1096 | run: *Run, | |
| 1097 | run_index: Configuration.Step.Index, | |
| 1098 | maker: *Maker, | |
| 1099 | progress_node: std.Progress.Node, | |
| 1100 | spawn_options: process.SpawnOptions, | |
| 1101 | fuzz_context: ?FuzzContext, | |
| 1102 | ) !void { | |
| 1103 | if (fuzz_context != null) { | |
| 1104 | try evalFuzzTest(run, run_index, progress_node, spawn_options, fuzz_context.?); | |
| 1105 | return; | |
| 1106 | } | |
| 1107 | ||
| 1108 | const graph = maker.graph; | |
| 1109 | const gpa = maker.gpa; | |
| 1110 | const io = graph.io; | |
| 1111 | const step = maker.stepByIndex(run_index); | |
| 1112 | ||
| 1113 | // We will update this every time a child runs. | |
| 1114 | step.result_peak_rss = 0; | |
| 1115 | ||
| 1116 | var test_results: Step.TestResults = .{ | |
| 1117 | .test_count = 0, | |
| 1118 | .skip_count = 0, | |
| 1119 | .fail_count = 0, | |
| 1120 | .crash_count = 0, | |
| 1121 | .timeout_count = 0, | |
| 1122 | .leak_count = 0, | |
| 1123 | .log_err_count = 0, | |
| 1124 | }; | |
| 1125 | var test_metadata: ?TestMetadata = null; | |
| 1126 | ||
| 1127 | while (true) { | |
| 1128 | var child = try process.spawn(io, spawn_options); | |
| 1129 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 1130 | var multi_reader: Io.File.MultiReader = undefined; | |
| 1131 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); | |
| 1132 | var child_killed = false; | |
| 1133 | defer if (!child_killed) { | |
| 1134 | child.kill(io); | |
| 1135 | multi_reader.deinit(); | |
| 1136 | step.result_peak_rss = @max( | |
| 1137 | step.result_peak_rss, | |
| 1138 | child.resource_usage_statistics.getMaxRss() orelse 0, | |
| 1139 | ); | |
| 1140 | }; | |
| 1141 | ||
| 1142 | switch (try waitZigTest( | |
| 1143 | arena, | |
| 1144 | run, | |
| 1145 | run_index, | |
| 1146 | maker, | |
| 1147 | &child, | |
| 1148 | progress_node, | |
| 1149 | &multi_reader, | |
| 1150 | &test_metadata, | |
| 1151 | &test_results, | |
| 1152 | )) { | |
| 1153 | .write_failed => |err| { | |
| 1154 | // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured | |
| 1155 | // all available stderr to make our error output as useful as possible. | |
| 1156 | const stderr_fr = multi_reader.fileReader(1); | |
| 1157 | while (stderr_fr.interface.fillMore()) |_| {} else |e| switch (e) { | |
| 1158 | error.ReadFailed => return stderr_fr.err.?, | |
| 1159 | error.EndOfStream => {}, | |
| 1160 | } | |
| 1161 | step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); | |
| 1162 | ||
| 1163 | // Clean up everything and wait for the child to exit. | |
| 1164 | child.stdin.?.close(io); | |
| 1165 | child.stdin = null; | |
| 1166 | multi_reader.deinit(); | |
| 1167 | child_killed = true; | |
| 1168 | const term = try child.wait(io); | |
| 1169 | step.result_peak_rss = @max( | |
| 1170 | step.result_peak_rss, | |
| 1171 | child.resource_usage_statistics.getMaxRss() orelse 0, | |
| 1172 | ); | |
| 1173 | ||
| 1174 | // The individual unit test results are irrelevant: the test runner itself broke! | |
| 1175 | // Fail immediately without populating `s.test_results`. | |
| 1176 | return step.fail(maker, "unable to write stdin ({t}); test process unexpectedly {f}", .{ | |
| 1177 | err, fmtTerm(term), | |
| 1178 | }); | |
| 1179 | }, | |
| 1180 | .no_poll => |no_poll| { | |
| 1181 | // This might be a success (we requested exit and the child dutifully closed stdout) or | |
| 1182 | // a crash of some kind. Either way, the child will terminate by itself -- wait for it. | |
| 1183 | const stderr_reader = multi_reader.reader(1); | |
| 1184 | const stderr_owned = try arena.dupe(u8, stderr_reader.buffered()); | |
| 1185 | ||
| 1186 | // Clean up everything and wait for the child to exit. | |
| 1187 | child.stdin.?.close(io); | |
| 1188 | child.stdin = null; | |
| 1189 | multi_reader.deinit(); | |
| 1190 | child_killed = true; | |
| 1191 | const term = try child.wait(io); | |
| 1192 | step.result_peak_rss = @max( | |
| 1193 | step.result_peak_rss, | |
| 1194 | child.resource_usage_statistics.getMaxRss() orelse 0, | |
| 1195 | ); | |
| 1196 | ||
| 1197 | if (no_poll.active_test_index) |test_index| { | |
| 1198 | // A test was running, so this is definitely a crash. Report it against that | |
| 1199 | // test, and continue to the next test. | |
| 1200 | test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed; | |
| 1201 | test_results.crash_count += 1; | |
| 1202 | try step.addError(maker, "'{s}' {f}{s}{s}", .{ | |
| 1203 | test_metadata.?.testName(test_index), | |
| 1204 | fmtTerm(term), | |
| 1205 | if (stderr_owned.len != 0) " with stderr:\n" else "", | |
| 1206 | std.mem.trim(u8, stderr_owned, "\n"), | |
| 1207 | }); | |
| 1208 | continue; | |
| 1209 | } | |
| 1210 | ||
| 1211 | // Report an error if the child terminated uncleanly or if we were still trying to run more tests. | |
| 1212 | step.result_stderr = stderr_owned; | |
| 1213 | const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32); | |
| 1214 | if (!tests_done or !termMatches(.{ .exited = 0 }, term)) { | |
| 1215 | // The individual unit test results are irrelevant: the test runner itself broke! | |
| 1216 | // Fail immediately without populating `s.test_results`. | |
| 1217 | return step.fail(maker, "test process unexpectedly {f}", .{fmtTerm(term)}); | |
| 1218 | } | |
| 1219 | ||
| 1220 | // We're done with all of the tests! Commit the test results and return. | |
| 1221 | step.test_results = test_results; | |
| 1222 | if (test_metadata) |tm| { | |
| 1223 | run.cached_test_metadata = tm.toCachedTestMetadata(); | |
| 1224 | if (maker.web_server) |*ws| { | |
| 1225 | if (graph.time_report) { | |
| 1226 | ws.updateTimeReportRunTest( | |
| 1227 | run_index, | |
| 1228 | &run.cached_test_metadata.?, | |
| 1229 | tm.ns_per_test, | |
| 1230 | ); | |
| 1231 | } | |
| 1232 | } | |
| 1233 | } | |
| 1234 | return; | |
| 1235 | }, | |
| 1236 | .timeout => |timeout| { | |
| 1237 | const stderr_reader = multi_reader.reader(1); | |
| 1238 | const stderr = stderr_reader.buffered(); | |
| 1239 | stderr_reader.tossBuffered(); | |
| 1240 | if (timeout.active_test_index) |test_index| { | |
| 1241 | // A test was running. Report the timeout against that test, and continue on to | |
| 1242 | // the next test. | |
| 1243 | test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed; | |
| 1244 | test_results.timeout_count += 1; | |
| 1245 | try step.addError(maker, "'{s}' timed out after {f}{s}{s}", .{ | |
| 1246 | test_metadata.?.testName(test_index), | |
| 1247 | Io.Duration{ .nanoseconds = timeout.ns_elapsed }, | |
| 1248 | if (stderr.len != 0) " with stderr:\n" else "", | |
| 1249 | std.mem.trim(u8, stderr, "\n"), | |
| 1250 | }); | |
| 1251 | continue; | |
| 1252 | } | |
| 1253 | // Just log an error and let the child be killed. | |
| 1254 | step.result_stderr = try arena.dupe(u8, stderr); | |
| 1255 | // The individual unit test results in `results` are irrelevant: the test runner | |
| 1256 | // is broken! Fail immediately without populating `s.test_results`. | |
| 1257 | return step.fail(maker, "test runner failed to respond for {f}", .{Io.Duration{ .nanoseconds = timeout.ns_elapsed }}); | |
| 1258 | }, | |
| 1259 | } | |
| 1260 | comptime unreachable; | |
| 1261 | } | |
| 1262 | } | |
| 1263 | ||
| 1264 | const TestMetadata = struct { | |
| 1265 | names: []const u32, | |
| 1266 | ns_per_test: []u64, | |
| 1267 | expected_panic_msgs: []const u32, | |
| 1268 | string_bytes: []const u8, | |
| 1269 | next_index: u32, | |
| 1270 | prog_node: std.Progress.Node, | |
| 1271 | ||
| 1272 | fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata { | |
| 1273 | return .{ | |
| 1274 | .names = tm.names, | |
| 1275 | .string_bytes = tm.string_bytes, | |
| 1276 | }; | |
| 1277 | } | |
| 1278 | ||
| 1279 | fn testName(tm: TestMetadata, index: u32) []const u8 { | |
| 1280 | return tm.toCachedTestMetadata().testName(index); | |
| 1281 | } | |
| 1282 | }; | |
| 1283 | ||
| 1284 | pub const CachedTestMetadata = struct { | |
| 1285 | names: []const u32, | |
| 1286 | string_bytes: []const u8, | |
| 1287 | ||
| 1288 | pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 { | |
| 1289 | return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); | |
| 1290 | } | |
| 1291 | }; | |
| 1292 | ||
| 1293 | fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { | |
| 1294 | while (metadata.next_index < metadata.names.len) { | |
| 1295 | const i = metadata.next_index; | |
| 1296 | metadata.next_index += 1; | |
| 1297 | ||
| 1298 | if (metadata.expected_panic_msgs[i] != 0) continue; | |
| 1299 | ||
| 1300 | const name = metadata.testName(i); | |
| 1301 | if (sub_prog_node.*) |n| n.end(); | |
| 1302 | sub_prog_node.* = metadata.prog_node.start(name, 0); | |
| 1303 | ||
| 1304 | try sendRunTestMessage(io, in, .run_test, i); | |
| 1305 | return; | |
| 1306 | } else { | |
| 1307 | metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done | |
| 1308 | try sendMessage(io, in, .exit); | |
| 1309 | } | |
| 1310 | } | |
| 1311 | ||
| 1312 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | |
| 1313 | const header: std.zig.Client.Message.Header = .{ | |
| 1314 | .tag = tag, | |
| 1315 | .bytes_len = 0, | |
| 1316 | }; | |
| 1317 | var w = file.writerStreaming(io, &.{}); | |
| 1318 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 1319 | error.WriteFailed => return w.err.?, | |
| 1320 | }; | |
| 1321 | } | |
| 1322 | ||
| 1323 | fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void { | |
| 1324 | const header: std.zig.Client.Message.Header = .{ | |
| 1325 | .tag = tag, | |
| 1326 | .bytes_len = 4, | |
| 1327 | }; | |
| 1328 | var w = file.writerStreaming(io, &.{}); | |
| 1329 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 1330 | error.WriteFailed => return w.err.?, | |
| 1331 | }; | |
| 1332 | w.interface.writeInt(u32, index, .little) catch |err| switch (err) { | |
| 1333 | error.WriteFailed => return w.err.?, | |
| 1334 | }; | |
| 1335 | } | |
| 1336 | ||
| 1337 | fn sendRunFuzzTestMessage( | |
| 1338 | io: Io, | |
| 1339 | file: Io.File, | |
| 1340 | test_names: []const []const u8, | |
| 1341 | kind: std.Build.abi.fuzz.LimitKind, | |
| 1342 | amount_or_instance: u64, | |
| 1343 | ) !void { | |
| 1344 | const header: std.zig.Client.Message.Header = .{ | |
| 1345 | .tag = .start_fuzzing, | |
| 1346 | .bytes_len = 1 + 8 + 4 + count: { | |
| 1347 | var c: u32 = @intCast(test_names.len * 4); | |
| 1348 | for (test_names) |name| { | |
| 1349 | c += @intCast(name.len); | |
| 1350 | } | |
| 1351 | break :count c; | |
| 1352 | }, | |
| 1353 | }; | |
| 1354 | var w = file.writerStreaming(io, &.{}); | |
| 1355 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 1356 | error.WriteFailed => return w.err.?, | |
| 1357 | }; | |
| 1358 | w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) { | |
| 1359 | error.WriteFailed => return w.err.?, | |
| 1360 | }; | |
| 1361 | w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) { | |
| 1362 | error.WriteFailed => return w.err.?, | |
| 1363 | }; | |
| 1364 | w.interface.writeInt(u32, @intCast(test_names.len), .little) catch |err| switch (err) { | |
| 1365 | error.WriteFailed => return w.err.?, | |
| 1366 | }; | |
| 1367 | for (test_names) |test_name| { | |
| 1368 | w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) { | |
| 1369 | error.WriteFailed => return w.err.?, | |
| 1370 | }; | |
| 1371 | w.interface.writeAll(test_name) catch |err| switch (err) { | |
| 1372 | error.WriteFailed => return w.err.?, | |
| 1373 | }; | |
| 1374 | } | |
| 1375 | } | |
| 1376 | ||
| 1377 | /// Uses `arena` to allocate the result. | |
| 1378 | fn evalGeneric( | |
| 1379 | arena: Allocator, | |
| 1380 | run_index: Configuration.Step.Index, | |
| 1381 | maker: *Maker, | |
| 1382 | spawn_options: process.SpawnOptions, | |
| 1383 | ) !EvalGenericResult { | |
| 1384 | const graph = maker.graph; | |
| 1385 | const io = graph.io; | |
| 1386 | const conf = &maker.scanned_config.configuration; | |
| 1387 | const conf_step = run_index.ptr(conf); | |
| 1388 | const conf_run = conf_step.extended.get(conf.extra).run; | |
| 1389 | const step = maker.stepByIndex(run_index); | |
| 1390 | ||
| 1391 | var child = try process.spawn(io, spawn_options); | |
| 1392 | defer child.kill(io); | |
| 1393 | ||
| 1394 | switch (conf_run.stdin.u) { | |
| 1395 | .bytes => |bytes| { | |
| 1396 | child.stdin.?.writeStreamingAll(io, bytes.slice(conf)) catch |err| { | |
| 1397 | return step.fail(maker, "failed to write stdin: {t}", .{err}); | |
| 1398 | }; | |
| 1399 | child.stdin.?.close(io); | |
| 1400 | child.stdin = null; | |
| 1401 | }, | |
| 1402 | .lazy_path => |lazy_path| { | |
| 1403 | const path = try maker.resolveLazyPathIndex(arena, lazy_path, run_index); | |
| 1404 | const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| { | |
| 1405 | return step.fail(maker, "failed to open stdin file: {t}", .{err}); | |
| 1406 | }; | |
| 1407 | defer file.close(io); | |
| 1408 | // TODO https://github.com/ziglang/zig/issues/23955 | |
| 1409 | var read_buffer: [1024]u8 = undefined; | |
| 1410 | var file_reader = file.reader(io, &read_buffer); | |
| 1411 | var write_buffer: [1024]u8 = undefined; | |
| 1412 | var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer); | |
| 1413 | _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { | |
| 1414 | error.ReadFailed => return step.fail(maker, "failed to read from {f}: {t}", .{ | |
| 1415 | path, file_reader.err.?, | |
| 1416 | }), | |
| 1417 | error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{ | |
| 1418 | stdin_writer.err.?, | |
| 1419 | }), | |
| 1420 | }; | |
| 1421 | stdin_writer.interface.flush() catch |err| switch (err) { | |
| 1422 | error.WriteFailed => return step.fail(maker, "failed to write to stdin: {t}", .{ | |
| 1423 | stdin_writer.err.?, | |
| 1424 | }), | |
| 1425 | }; | |
| 1426 | child.stdin.?.close(io); | |
| 1427 | child.stdin = null; | |
| 1428 | }, | |
| 1429 | .none => {}, | |
| 1430 | } | |
| 1431 | ||
| 1432 | var stdout_bytes: ?[]const u8 = null; | |
| 1433 | var stderr_bytes: ?[]const u8 = null; | |
| 1434 | ||
| 1435 | if (child.stdout) |stdout| { | |
| 1436 | if (child.stderr) |stderr| { | |
| 1437 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 1438 | var multi_reader: Io.File.MultiReader = undefined; | |
| 1439 | multi_reader.init(arena, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr }); | |
| 1440 | ||
| 1441 | const stdout_reader = multi_reader.reader(0); | |
| 1442 | const stderr_reader = multi_reader.reader(1); | |
| 1443 | ||
| 1444 | while (multi_reader.fill(64, .none)) |_| { | |
| 1445 | if (conf_run.stdio_limit.value) |limit| { | |
| 1446 | if (stdout_reader.buffered().len > limit) | |
| 1447 | return error.StdoutStreamTooLong; | |
| 1448 | if (stderr_reader.buffered().len > limit) | |
| 1449 | return error.StderrStreamTooLong; | |
| 1450 | } | |
| 1451 | } else |err| switch (err) { | |
| 1452 | error.Timeout => unreachable, | |
| 1453 | error.EndOfStream => {}, | |
| 1454 | else => |e| return e, | |
| 1455 | } | |
| 1456 | ||
| 1457 | try multi_reader.checkAnyError(); | |
| 1458 | ||
| 1459 | stdout_bytes = try multi_reader.toOwnedSlice(0); | |
| 1460 | stderr_bytes = try multi_reader.toOwnedSlice(1); | |
| 1461 | } else { | |
| 1462 | var stdout_reader = stdout.readerStreaming(io, &.{}); | |
| 1463 | const stdio_limit: Io.Limit = if (conf_run.stdio_limit.value) |x| .limited(x) else .unlimited; | |
| 1464 | stdout_bytes = stdout_reader.interface.allocRemaining(arena, stdio_limit) catch |err| switch (err) { | |
| 1465 | error.OutOfMemory => |e| return e, | |
| 1466 | error.ReadFailed => return stdout_reader.err.?, | |
| 1467 | error.StreamTooLong => return error.StdoutStreamTooLong, | |
| 1468 | }; | |
| 1469 | } | |
| 1470 | } else if (child.stderr) |stderr| { | |
| 1471 | var stderr_reader = stderr.readerStreaming(io, &.{}); | |
| 1472 | const stdio_limit: Io.Limit = if (conf_run.stdio_limit.value) |x| .limited(x) else .unlimited; | |
| 1473 | stderr_bytes = stderr_reader.interface.allocRemaining(arena, stdio_limit) catch |err| switch (err) { | |
| 1474 | error.OutOfMemory => |e| return e, | |
| 1475 | error.ReadFailed => return stderr_reader.err.?, | |
| 1476 | error.StreamTooLong => return error.StderrStreamTooLong, | |
| 1477 | }; | |
| 1478 | } | |
| 1479 | ||
| 1480 | if (stderr_bytes) |bytes| if (bytes.len > 0) { | |
| 1481 | // Treat stderr as an error message. | |
| 1482 | const stderr_is_diagnostic = conf_run.captured_stderr.value == null and switch (conf_run.flags.stdio) { | |
| 1483 | .check => !checksContainStderr(&conf_run), | |
| 1484 | else => true, | |
| 1485 | }; | |
| 1486 | if (stderr_is_diagnostic) { | |
| 1487 | step.result_stderr = bytes; | |
| 1488 | } | |
| 1489 | }; | |
| 1490 | ||
| 1491 | step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 1492 | ||
| 1493 | return .{ | |
| 1494 | .term = try child.wait(io), | |
| 1495 | .stdout = stdout_bytes, | |
| 1496 | .stderr = stderr_bytes, | |
| 1497 | }; | |
| 1498 | } | |
| 1499 | ||
| 1500 | const IndexedOutput = struct { | |
| 1501 | index: u32, | |
| 1502 | arg_index: Configuration.Step.Run.Arg.Index, | |
| 1503 | }; | |
| 1504 | ||
| 1505 | pub fn rerunInFuzzMode( | |
| 1506 | run: *Run, | |
| 1507 | run_index: Configuration.Step.Index, | |
| 1508 | fuzz: *Fuzz, | |
| 1509 | prog_node: std.Progress.Node, | |
| 1510 | ) !void { | |
| 1511 | const maker = fuzz.maker; | |
| 1512 | const graph = maker.graph; | |
| 1513 | const step = maker.stepByIndex(run_index); | |
| 1514 | const io = graph.io; | |
| 1515 | const gpa = maker.gpa; | |
| 1516 | const conf = &maker.scanned_config.configuration; | |
| 1517 | const conf_step = run_index.ptr(conf); | |
| 1518 | const conf_run = conf_step.extended.get(conf.extra).run; | |
| 1519 | const cache_root = graph.local_cache_root; | |
| 1520 | ||
| 1521 | var arena_allocator: std.heap.ArenaAllocator = .init(gpa); | |
| 1522 | defer arena_allocator.deinit(); | |
| 1523 | const arena = arena_allocator.allocator(); | |
| 1524 | ||
| 1525 | var argv_list: std.ArrayList([]const u8) = .empty; | |
| 1526 | defer argv_list.deinit(gpa); | |
| 1527 | ||
| 1528 | for (conf_run.args.slice) |arg_index| { | |
| 1529 | const arg = arg_index.get(conf); | |
| 1530 | try argv_list.ensureUnusedCapacity(gpa, 1); | |
| 1531 | switch (arg.flags.tag) { | |
| 1532 | .string => { | |
| 1533 | const prefix = arg.prefix.value.?.slice(conf); | |
| 1534 | argv_list.appendAssumeCapacity(prefix); | |
| 1535 | }, | |
| 1536 | .path_file => { | |
| 1537 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 1538 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 1539 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); | |
| 1540 | argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ | |
| 1541 | prefix, try convertPathArg(arena, run_index, maker, file_path), suffix, | |
| 1542 | })); | |
| 1543 | }, | |
| 1544 | .path_directory => { | |
| 1545 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 1546 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 1547 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); | |
| 1548 | const resolved_arg = try mem.concat(arena, u8, &.{ | |
| 1549 | prefix, try convertPathArg(arena, run_index, maker, file_path), suffix, | |
| 1550 | }); | |
| 1551 | argv_list.appendAssumeCapacity(resolved_arg); | |
| 1552 | }, | |
| 1553 | .file_content => { | |
| 1554 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 1555 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 1556 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); | |
| 1557 | ||
| 1558 | var result: std.Io.Writer.Allocating = .init(arena); | |
| 1559 | result.writer.writeAll(prefix) catch return error.OutOfMemory; | |
| 1560 | ||
| 1561 | const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| | |
| 1562 | return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err }); | |
| 1563 | defer file.close(io); | |
| 1564 | ||
| 1565 | var file_reader = file.reader(io, &.{}); | |
| 1566 | _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { | |
| 1567 | error.ReadFailed => switch (file_reader.err.?) { | |
| 1568 | error.Canceled => |e| return e, | |
| 1569 | else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }), | |
| 1570 | }, | |
| 1571 | error.WriteFailed => return error.OutOfMemory, | |
| 1572 | }; | |
| 1573 | result.writer.writeAll(suffix) catch return error.OutOfMemory; | |
| 1574 | ||
| 1575 | argv_list.appendAssumeCapacity(result.written()); | |
| 1576 | }, | |
| 1577 | .artifact => { | |
| 1578 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 1579 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 1580 | const producer_index = arg.producer.value.?; | |
| 1581 | const producer_step = producer_index.ptr(conf); | |
| 1582 | const producer = producer_step.extended.get(conf.extra).compile; | |
| 1583 | const producer_make_comp_step = maker.stepByIndex(producer_index); | |
| 1584 | const producer_make_comp = &producer_make_comp_step.extended.compile; | |
| 1585 | const file_path: Path = if (producer_index == conf_run.producer.value.?) | |
| 1586 | run.rebuilt_executable.? | |
| 1587 | else | |
| 1588 | producer_make_comp.installed_path orelse | |
| 1589 | maker.generatedPath(producer.generated_bin.value.?).*; | |
| 1590 | argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ | |
| 1591 | prefix, try convertPathArg(arena, run_index, maker, file_path), suffix, | |
| 1592 | })); | |
| 1593 | }, | |
| 1594 | .output_file => unreachable, | |
| 1595 | .output_directory => unreachable, | |
| 1596 | .passthru => unreachable, | |
| 1597 | } | |
| 1598 | } | |
| 1599 | ||
| 1600 | if (conf_run.flags.test_runner_mode) { | |
| 1601 | const cache_dir_string = try convertPathArg(arena, run_index, maker, .{ .root_dir = cache_root }); | |
| 1602 | ||
| 1603 | try argv_list.ensureUnusedCapacity(gpa, 3); | |
| 1604 | argv_list.appendAssumeCapacity(try allocPrint(arena, "--cache-dir={s}", .{cache_dir_string})); | |
| 1605 | argv_list.appendAssumeCapacity(try allocPrint(arena, "--seed=0x{x}", .{graph.random_seed})); | |
| 1606 | argv_list.appendAssumeCapacity("--listen=-"); | |
| 1607 | } | |
| 1608 | ||
| 1609 | step.clearFailedCommand(gpa); | |
| 1610 | ||
| 1611 | const has_side_effects = false; | |
| 1612 | var rand_int: u64 = undefined; | |
| 1613 | io.random(@ptrCast(&rand_int)); | |
| 1614 | const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); | |
| 1615 | try runCommand(arena, run, run_index, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{ | |
| 1616 | .fuzz = fuzz, | |
| 1617 | }); | |
| 1618 | } | |
| 1619 | ||
| 1620 | fn populateGeneratedPaths( | |
| 1621 | maker: *Maker, | |
| 1622 | output_placeholders: []const IndexedOutput, | |
| 1623 | cache_root: Cache.Directory, | |
| 1624 | digest: *const Cache.HexDigest, | |
| 1625 | ) !void { | |
| 1626 | const conf = &maker.scanned_config.configuration; | |
| 1627 | const graph = maker.graph; | |
| 1628 | ||
| 1629 | for (output_placeholders) |placeholder| { | |
| 1630 | const arg = placeholder.arg_index.get(conf); | |
| 1631 | maker.generatedPath(arg.generated.value.?).* = .{ | |
| 1632 | .root_dir = cache_root, | |
| 1633 | .sub_path = try Dir.path.join(graph.arena, &.{ | |
| 1634 | "o", digest, arg.basename.value.?.slice(conf), | |
| 1635 | }), | |
| 1636 | }; | |
| 1637 | } | |
| 1638 | } | |
| 1639 | ||
| 1640 | fn populateGeneratedPathsCreateDirs( | |
| 1641 | arena: Allocator, | |
| 1642 | run_index: Configuration.Step.Index, | |
| 1643 | maker: *Maker, | |
| 1644 | output_dir_path: []const u8, | |
| 1645 | output_placeholders: []const IndexedOutput, | |
| 1646 | argv: [][]const u8, | |
| 1647 | ) !void { | |
| 1648 | const step = maker.stepByIndex(run_index); | |
| 1649 | const conf = &maker.scanned_config.configuration; | |
| 1650 | const graph = maker.graph; | |
| 1651 | const io = graph.io; | |
| 1652 | const cache_root = graph.local_cache_root; | |
| 1653 | ||
| 1654 | for (output_placeholders) |placeholder| { | |
| 1655 | const arg = placeholder.arg_index.get(conf); | |
| 1656 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; | |
| 1657 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; | |
| 1658 | const basename = arg.basename.value.?.slice(conf); | |
| 1659 | ||
| 1660 | const generated_path: Path = .{ | |
| 1661 | .root_dir = cache_root, | |
| 1662 | .sub_path = try Dir.path.join(graph.arena, &.{ output_dir_path, basename }), | |
| 1663 | }; | |
| 1664 | const create_path: Path = .{ | |
| 1665 | .root_dir = cache_root, | |
| 1666 | .sub_path = switch (arg.flags.tag) { | |
| 1667 | .output_file => Dir.path.dirname(generated_path.sub_path).?, | |
| 1668 | .output_directory => generated_path.sub_path, | |
| 1669 | else => unreachable, | |
| 1670 | }, | |
| 1671 | }; | |
| 1672 | create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err| | |
| 1673 | return step.fail(maker, "unable to make path {f}: {t}", .{ create_path, err }); | |
| 1674 | ||
| 1675 | maker.generatedPath(arg.generated.value.?).* = generated_path; | |
| 1676 | ||
| 1677 | const arg_output_path = try convertPathArg(arena, run_index, maker, generated_path); | |
| 1678 | argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix }); | |
| 1679 | } | |
| 1680 | } | |
| 1681 | ||
| 1682 | fn populateGeneratedStdIo( | |
| 1683 | maker: *Maker, | |
| 1684 | conf_run: *const Configuration.Step.Run, | |
| 1685 | cache_root: Cache.Directory, | |
| 1686 | digest: *const Cache.HexDigest, | |
| 1687 | ) !void { | |
| 1688 | const conf = &maker.scanned_config.configuration; | |
| 1689 | const graph = maker.graph; | |
| 1690 | ||
| 1691 | if (conf_run.captured_stdout.value) |captured| { | |
| 1692 | maker.generatedPath(captured.generated_file).* = .{ | |
| 1693 | .root_dir = cache_root, | |
| 1694 | .sub_path = try Dir.path.join(graph.arena, &.{ | |
| 1695 | "o", digest, captured.basename.slice(conf), | |
| 1696 | }), | |
| 1697 | }; | |
| 1698 | } | |
| 1699 | ||
| 1700 | if (conf_run.captured_stderr.value) |captured| { | |
| 1701 | maker.generatedPath(captured.generated_file).* = .{ | |
| 1702 | .root_dir = cache_root, | |
| 1703 | .sub_path = try Dir.path.join(graph.arena, &.{ | |
| 1704 | "o", digest, captured.basename.slice(conf), | |
| 1705 | }), | |
| 1706 | }; | |
| 1707 | } | |
| 1708 | } | |
| 1709 | ||
| 1710 | fn formatTerm(term: ?process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 1711 | if (term) |t| switch (t) { | |
| 1712 | .exited => |code| try w.print("exited with code {d}", .{code}), | |
| 1713 | .signal => |sig| try w.print("terminated with signal {t}", .{sig}), | |
| 1714 | .stopped => |sig| try w.print("stopped with signal {t}", .{sig}), | |
| 1715 | .unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}), | |
| 1716 | } else { | |
| 1717 | try w.writeAll("exited with any code"); | |
| 1718 | } | |
| 1719 | } | |
| 1720 | fn fmtTerm(term: ?process.Child.Term) std.fmt.Alt(?process.Child.Term, formatTerm) { | |
| 1721 | return .{ .data = term }; | |
| 1722 | } | |
| 1723 | ||
| 1724 | const FuzzContext = struct { | |
| 1725 | fuzz: *Fuzz, | |
| 1726 | }; | |
| 1727 | ||
| 1728 | fn runCommand( | |
| 1729 | arena: Allocator, | |
| 1730 | run: *Run, | |
| 1731 | run_index: Configuration.Step.Index, | |
| 1732 | maker: *Maker, | |
| 1733 | progress_node: std.Progress.Node, | |
| 1734 | argv: []const []const u8, | |
| 1735 | has_side_effects: bool, | |
| 1736 | output_dir_path: []const u8, | |
| 1737 | fuzz_context: ?FuzzContext, | |
| 1738 | ) Step.ExtendedMakeError!void { | |
| 1739 | const graph = maker.graph; | |
| 1740 | const gpa = maker.gpa; | |
| 1741 | const step = maker.stepByIndex(run_index); | |
| 1742 | const io = graph.io; | |
| 1743 | const cache_root = graph.local_cache_root; | |
| 1744 | const conf = &maker.scanned_config.configuration; | |
| 1745 | const conf_step = run_index.ptr(conf); | |
| 1746 | const conf_run = conf_step.extended.get(conf.extra).run; | |
| 1747 | ||
| 1748 | const cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd| | |
| 1749 | .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) } | |
| 1750 | else | |
| 1751 | .inherit; | |
| 1752 | ||
| 1753 | const allow_skip = switch (conf_run.flags.stdio) { | |
| 1754 | .check, .zig_test => conf_run.flags.skip_foreign_checks, | |
| 1755 | else => false, | |
| 1756 | }; | |
| 1757 | ||
| 1758 | var interp_argv: std.ArrayList([]const u8) = .empty; | |
| 1759 | ||
| 1760 | var environ_map: std.process.Environ.Map = .init(gpa); | |
| 1761 | defer environ_map.deinit(); | |
| 1762 | ||
| 1763 | // In either case we add to this mutatable data structure so that we can | |
| 1764 | // tweak the environment below. | |
| 1765 | if (conf_run.environ_map.value) |env_map_index| { | |
| 1766 | const conf_env_map = env_map_index.get(conf); | |
| 1767 | for (conf_env_map.keys.slice(conf), conf_env_map.values.slice(conf)) |k, v| { | |
| 1768 | try environ_map.put(k.slice(conf), v.slice(conf)); | |
| 1769 | } | |
| 1770 | } else { | |
| 1771 | try environ_map.putAll(&graph.environ_map); | |
| 1772 | } | |
| 1773 | ||
| 1774 | // Now that we have the environ map, we might need to mutate it to insert | |
| 1775 | // .dll search paths because Windows doesn't have rpaths. | |
| 1776 | const arg0 = conf_run.args.slice[0].get(conf); | |
| 1777 | if (arg0.producer.value) |producer_index| { | |
| 1778 | const producer_step = producer_index.ptr(conf); | |
| 1779 | const producer = producer_step.extended.get(conf.extra).compile; | |
| 1780 | const root_module = producer.root_module.get(conf); | |
| 1781 | const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf); | |
| 1782 | if (root_module_target.flags.os_tag == .windows) { | |
| 1783 | try addPathForDynLibs(maker, arena, producer_index, &environ_map, argv[0]); | |
| 1784 | } | |
| 1785 | } | |
| 1786 | ||
| 1787 | const cwd_string = switch (cwd) { | |
| 1788 | .path => |p| p, | |
| 1789 | .dir => unreachable, | |
| 1790 | .inherit => null, | |
| 1791 | }; | |
| 1792 | try graph.handleVerbose(cwd_string, &environ_map, argv); | |
| 1793 | ||
| 1794 | const opt_generic_result = spawnChildAndCollect( | |
| 1795 | arena, | |
| 1796 | run_index, | |
| 1797 | run, | |
| 1798 | maker, | |
| 1799 | progress_node, | |
| 1800 | argv, | |
| 1801 | &environ_map, | |
| 1802 | has_side_effects, | |
| 1803 | fuzz_context, | |
| 1804 | ) catch |err| term: { | |
| 1805 | switch (err) { | |
| 1806 | error.InvalidExe, // cpu arch mismatch | |
| 1807 | error.FileNotFound, // can happen with a wrong dynamic linker path | |
| 1808 | => interpret: { | |
| 1809 | const producer_index = arg0.producer.value orelse break :interpret; | |
| 1810 | const producer_step = producer_index.ptr(conf); | |
| 1811 | const producer = producer_step.extended.get(conf.extra).compile; | |
| 1812 | switch (producer.flags3.kind) { | |
| 1813 | .exe, .@"test" => {}, | |
| 1814 | else => break :interpret, | |
| 1815 | } | |
| 1816 | const root_module = producer.root_module.get(conf); | |
| 1817 | const root_module_target = root_module.resolved_target.get(conf).?.result.get(conf); | |
| 1818 | const other_target_query = root_module_target.unwrap(conf); | |
| 1819 | const root_target = std.zig.system.resolveTargetQuery(io, other_target_query) catch unreachable; | |
| 1820 | const link_libc = maker.stepByIndex(producer_index).extended.compile.is_linking_libc; | |
| 1821 | ||
| 1822 | const host: std.Target = std.zig.system.resolveTargetQuery(io, .{}) catch |he| switch (he) { | |
| 1823 | error.Canceled => |e| return e, | |
| 1824 | else => builtin.target, | |
| 1825 | }; | |
| 1826 | ||
| 1827 | const need_cross_libc = link_libc and root_target.os.tag == .linux and | |
| 1828 | switch (producer.flags2.linkage) { | |
| 1829 | .static => false, | |
| 1830 | .dynamic => true, | |
| 1831 | .default => root_target.isGnuLibC(), | |
| 1832 | }; | |
| 1833 | switch (std.zig.system.getExternalExecutor(io, &root_target, .{ | |
| 1834 | .host_cpu_arch = host.cpu.arch, | |
| 1835 | .host_os_tag = host.os.tag, | |
| 1836 | .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null, | |
| 1837 | .link_libc = link_libc, | |
| 1838 | })) { | |
| 1839 | .native, .rosetta => { | |
| 1840 | if (allow_skip) return error.MakeSkipped; | |
| 1841 | break :interpret; | |
| 1842 | }, | |
| 1843 | .wine => |bin_name| { | |
| 1844 | if (graph.enable_wine) { | |
| 1845 | try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len); | |
| 1846 | interp_argv.appendAssumeCapacity(bin_name); | |
| 1847 | interp_argv.appendSliceAssumeCapacity(argv); | |
| 1848 | ||
| 1849 | // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but | |
| 1850 | // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired. | |
| 1851 | if (environ_map.get("WINEDEBUG") == null) { | |
| 1852 | try environ_map.put("WINEDEBUG", "-all"); | |
| 1853 | } | |
| 1854 | } else { | |
| 1855 | return failForeign(arena, &conf_run, maker, run_index, "-fwine", argv[0], &root_target, &host); | |
| 1856 | } | |
| 1857 | }, | |
| 1858 | .qemu => |bin_name| { | |
| 1859 | if (graph.enable_qemu) { | |
| 1860 | try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len); | |
| 1861 | interp_argv.appendAssumeCapacity(bin_name); | |
| 1862 | ||
| 1863 | if (need_cross_libc) { | |
| 1864 | if (graph.libc_runtimes_dir) |dir| { | |
| 1865 | interp_argv.appendAssumeCapacity("-L"); | |
| 1866 | interp_argv.appendAssumeCapacity(try Dir.path.join(arena, &.{ | |
| 1867 | dir, | |
| 1868 | try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( | |
| 1869 | arena, | |
| 1870 | root_target.cpu.arch, | |
| 1871 | root_target.os.tag, | |
| 1872 | root_target.abi, | |
| 1873 | ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple( | |
| 1874 | arena, | |
| 1875 | root_target.cpu.arch, | |
| 1876 | root_target.abi, | |
| 1877 | ) else unreachable, | |
| 1878 | })); | |
| 1879 | } else return failForeign(arena, &conf_run, maker, run_index, "--libc-runtimes", argv[0], &root_target, &host); | |
| 1880 | } | |
| 1881 | ||
| 1882 | interp_argv.appendSliceAssumeCapacity(argv); | |
| 1883 | } else return failForeign(arena, &conf_run, maker, run_index, "-fqemu", argv[0], &root_target, &host); | |
| 1884 | }, | |
| 1885 | .darling => |bin_name| { | |
| 1886 | if (graph.enable_darling) { | |
| 1887 | try interp_argv.ensureUnusedCapacity(arena, 1 + argv.len); | |
| 1888 | interp_argv.appendAssumeCapacity(bin_name); | |
| 1889 | interp_argv.appendSliceAssumeCapacity(argv); | |
| 1890 | } else { | |
| 1891 | return failForeign(arena, &conf_run, maker, run_index, "-fdarling", argv[0], &root_target, &host); | |
| 1892 | } | |
| 1893 | }, | |
| 1894 | .wasmtime => |bin_name| { | |
| 1895 | if (graph.enable_wasmtime) { | |
| 1896 | try interp_argv.ensureUnusedCapacity(arena, 3 + argv.len); | |
| 1897 | interp_argv.appendAssumeCapacity(bin_name); | |
| 1898 | interp_argv.appendAssumeCapacity("--dir=."); | |
| 1899 | // Wasmtime doeesn't inherit environment variables from the parent process | |
| 1900 | // by default. '-S inherit-env' was added in Wasmtime version 20. | |
| 1901 | interp_argv.appendAssumeCapacity("-Sinherit-env"); | |
| 1902 | interp_argv.appendSliceAssumeCapacity(argv); | |
| 1903 | } else { | |
| 1904 | return failForeign(arena, &conf_run, maker, run_index, "-fwasmtime", argv[0], &root_target, &host); | |
| 1905 | } | |
| 1906 | }, | |
| 1907 | .bad_dl => |foreign_dl| { | |
| 1908 | if (allow_skip) return error.MakeSkipped; | |
| 1909 | ||
| 1910 | const host_dl = host.dynamic_linker.get() orelse "(none)"; | |
| 1911 | ||
| 1912 | return step.fail(maker, | |
| 1913 | \\the host system is unable to execute binaries from the target | |
| 1914 | \\ because the host dynamic linker is '{s}', | |
| 1915 | \\ while the target dynamic linker is '{s}'. | |
| 1916 | \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step | |
| 1917 | , .{ host_dl, foreign_dl }); | |
| 1918 | }, | |
| 1919 | .bad_os_or_cpu => { | |
| 1920 | if (allow_skip) return error.MakeSkipped; | |
| 1921 | ||
| 1922 | const host_name = try host.zigTriple(arena); | |
| 1923 | const foreign_name = try root_target.zigTriple(arena); | |
| 1924 | ||
| 1925 | return step.fail(maker, "the host system ({s}) is unable to execute binaries from the target ({s})", .{ | |
| 1926 | host_name, foreign_name, | |
| 1927 | }); | |
| 1928 | }, | |
| 1929 | } | |
| 1930 | ||
| 1931 | step.clearFailedCommand(gpa); | |
| 1932 | try graph.handleVerbose(cwd_string, &environ_map, interp_argv.items); | |
| 1933 | ||
| 1934 | break :term spawnChildAndCollect( | |
| 1935 | arena, | |
| 1936 | run_index, | |
| 1937 | run, | |
| 1938 | maker, | |
| 1939 | progress_node, | |
| 1940 | interp_argv.items, | |
| 1941 | &environ_map, | |
| 1942 | has_side_effects, | |
| 1943 | fuzz_context, | |
| 1944 | ) catch |e| { | |
| 1945 | if (!conf_run.flags.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; | |
| 1946 | if (e == error.MakeFailed) return error.MakeFailed; // error already reported | |
| 1947 | return step.fail(maker, "unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e }); | |
| 1948 | }; | |
| 1949 | }, | |
| 1950 | error.MakeFailed, error.OutOfMemory, error.Canceled => |e| return e, | |
| 1951 | else => {}, | |
| 1952 | } | |
| 1953 | return step.fail(maker, "failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err }); | |
| 1954 | }; | |
| 1955 | ||
| 1956 | const generic_result = opt_generic_result orelse { | |
| 1957 | assert(conf_run.flags.stdio == .zig_test); | |
| 1958 | // Specific errors have already been reported, and test results are populated. All we need | |
| 1959 | // to do is report step failure if any test failed. | |
| 1960 | if (!step.test_results.isSuccess()) return error.MakeFailed; | |
| 1961 | return; | |
| 1962 | }; | |
| 1963 | ||
| 1964 | assert(fuzz_context == null); | |
| 1965 | assert(conf_run.flags.stdio != .zig_test); | |
| 1966 | ||
| 1967 | // Capture stdout and stderr to GeneratedFile objects. | |
| 1968 | const Stream = struct { | |
| 1969 | captured: ?Configuration.Step.Run.CapturedStream, | |
| 1970 | bytes: ?[]const u8, | |
| 1971 | trim_whitespace: Configuration.Step.Run.TrimWhitespace, | |
| 1972 | }; | |
| 1973 | for (&[_]Stream{ | |
| 1974 | .{ | |
| 1975 | .captured = conf_run.captured_stdout.value, | |
| 1976 | .bytes = generic_result.stdout, | |
| 1977 | .trim_whitespace = conf_run.flags.stdout_trim_whitespace, | |
| 1978 | }, | |
| 1979 | .{ | |
| 1980 | .captured = conf_run.captured_stderr.value, | |
| 1981 | .bytes = generic_result.stderr, | |
| 1982 | .trim_whitespace = conf_run.flags.stderr_trim_whitespace, | |
| 1983 | }, | |
| 1984 | }) |*stream| { | |
| 1985 | if (stream.captured) |captured| { | |
| 1986 | const output_path: Path = .{ | |
| 1987 | .root_dir = cache_root, | |
| 1988 | .sub_path = try Dir.path.join(graph.arena, &.{ | |
| 1989 | output_dir_path, captured.basename.slice(conf), | |
| 1990 | }), | |
| 1991 | }; | |
| 1992 | maker.generatedPath(captured.generated_file).* = output_path; | |
| 1993 | ||
| 1994 | const sub_path_parent = output_path.dirname().?; | |
| 1995 | sub_path_parent.root_dir.handle.createDirPath(io, sub_path_parent.sub_path) catch |err| | |
| 1996 | return step.fail(maker, "unable to make path {f}: {t}", .{ sub_path_parent, err }); | |
| 1997 | ||
| 1998 | const data = switch (stream.trim_whitespace) { | |
| 1999 | .none => stream.bytes.?, | |
| 2000 | .all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace), | |
| 2001 | .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace), | |
| 2002 | .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace), | |
| 2003 | }; | |
| 2004 | output_path.root_dir.handle.writeFile(io, .{ | |
| 2005 | .sub_path = output_path.sub_path, | |
| 2006 | .data = data, | |
| 2007 | }) catch |err| return step.fail(maker, "unable to write file {f}: {t}", .{ output_path, err }); | |
| 2008 | } | |
| 2009 | } | |
| 2010 | ||
| 2011 | switch (conf_run.flags.stdio) { | |
| 2012 | .zig_test => unreachable, | |
| 2013 | .check => { | |
| 2014 | if (conf_run.expect_stderr_exact.value) |bytes| { | |
| 2015 | const expected_bytes = bytes.slice(conf); | |
| 2016 | if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) { | |
| 2017 | return step.fail(maker, | |
| 2018 | \\========= expected this stderr: ========= | |
| 2019 | \\{s} | |
| 2020 | \\========= but found: ==================== | |
| 2021 | \\{s} | |
| 2022 | , .{ | |
| 2023 | expected_bytes, | |
| 2024 | generic_result.stderr.?, | |
| 2025 | }); | |
| 2026 | } | |
| 2027 | } | |
| 2028 | if (conf_run.expect_stdout_exact.value) |bytes| { | |
| 2029 | const expected_bytes = bytes.slice(conf); | |
| 2030 | if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) { | |
| 2031 | return step.fail(maker, | |
| 2032 | \\========= expected this stdout: ========= | |
| 2033 | \\{s} | |
| 2034 | \\========= but found: ==================== | |
| 2035 | \\{s} | |
| 2036 | , .{ | |
| 2037 | expected_bytes, | |
| 2038 | generic_result.stdout.?, | |
| 2039 | }); | |
| 2040 | } | |
| 2041 | } | |
| 2042 | for (conf_run.expect_stderr_match.slice) |bytes| { | |
| 2043 | const match = bytes.slice(conf); | |
| 2044 | if (mem.find(u8, generic_result.stderr.?, match) == null) { | |
| 2045 | return step.fail(maker, | |
| 2046 | \\========= expected to find in stderr: ========= | |
| 2047 | \\{s} | |
| 2048 | \\========= but stderr does not contain it: ===== | |
| 2049 | \\{s} | |
| 2050 | , .{ | |
| 2051 | match, | |
| 2052 | generic_result.stderr.?, | |
| 2053 | }); | |
| 2054 | } | |
| 2055 | } | |
| 2056 | for (conf_run.expect_stdout_match.slice) |bytes| { | |
| 2057 | const match = bytes.slice(conf); | |
| 2058 | if (mem.find(u8, generic_result.stdout.?, match) == null) { | |
| 2059 | return step.fail(maker, | |
| 2060 | \\========= expected to find in stdout: ========= | |
| 2061 | \\{s} | |
| 2062 | \\========= but stdout does not contain it: ===== | |
| 2063 | \\{s} | |
| 2064 | , .{ | |
| 2065 | match, | |
| 2066 | generic_result.stdout.?, | |
| 2067 | }); | |
| 2068 | } | |
| 2069 | } | |
| 2070 | if (conf_run.expect_term_value.value) |expected_term_value| { | |
| 2071 | const expected_term: process.Child.Term = switch (conf_run.flags2.expect_term_status) { | |
| 2072 | .exited => .{ .exited = @intCast(expected_term_value) }, | |
| 2073 | .signal => .{ .signal = @enumFromInt(expected_term_value) }, | |
| 2074 | .stopped => .{ .stopped = @enumFromInt(expected_term_value) }, | |
| 2075 | .unknown => .{ .unknown = expected_term_value }, | |
| 2076 | }; | |
| 2077 | if (!termMatches(expected_term, generic_result.term)) { | |
| 2078 | return step.fail(maker, "process {f} (expected {f})", .{ | |
| 2079 | fmtTerm(generic_result.term), | |
| 2080 | fmtTerm(expected_term), | |
| 2081 | }); | |
| 2082 | } | |
| 2083 | } | |
| 2084 | }, | |
| 2085 | else => { | |
| 2086 | // On failure, report captured stderr like normal standard error output. | |
| 2087 | const bad_exit = switch (generic_result.term) { | |
| 2088 | .exited => |code| code != 0, | |
| 2089 | .signal, .stopped, .unknown => true, | |
| 2090 | }; | |
| 2091 | if (bad_exit) { | |
| 2092 | if (generic_result.stderr) |bytes| { | |
| 2093 | step.result_stderr = bytes; | |
| 2094 | } | |
| 2095 | } | |
| 2096 | ||
| 2097 | try step.handleChildProcessTerm(maker, generic_result.term); | |
| 2098 | }, | |
| 2099 | } | |
| 2100 | } | |
| 2101 | ||
| 2102 | const EvalGenericResult = struct { | |
| 2103 | term: process.Child.Term, | |
| 2104 | stdout: ?[]const u8, | |
| 2105 | stderr: ?[]const u8, | |
| 2106 | }; | |
| 2107 | ||
| 2108 | fn spawnChildAndCollect( | |
| 2109 | arena: Allocator, | |
| 2110 | run_index: Configuration.Step.Index, | |
| 2111 | run: *Run, | |
| 2112 | maker: *Maker, | |
| 2113 | progress_node: std.Progress.Node, | |
| 2114 | argv: []const []const u8, | |
| 2115 | environ_map: *EnvMap, | |
| 2116 | has_side_effects: bool, | |
| 2117 | fuzz_context: ?FuzzContext, | |
| 2118 | ) !?EvalGenericResult { | |
| 2119 | const step = maker.stepByIndex(run_index); | |
| 2120 | const graph = maker.graph; | |
| 2121 | const io = graph.io; | |
| 2122 | const gpa = maker.gpa; | |
| 2123 | const conf = &maker.scanned_config.configuration; | |
| 2124 | const conf_step = run_index.ptr(conf); | |
| 2125 | const conf_run = conf_step.extended.get(conf.extra).run; | |
| 2126 | ||
| 2127 | if (fuzz_context != null) { | |
| 2128 | assert(!has_side_effects); | |
| 2129 | assert(conf_run.flags.stdio == .zig_test); | |
| 2130 | } | |
| 2131 | ||
| 2132 | const child_cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd| | |
| 2133 | .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) } | |
| 2134 | else | |
| 2135 | .inherit; | |
| 2136 | ||
| 2137 | // If an error occurs, it's caused by this command: | |
| 2138 | step.clearFailedCommand(gpa); | |
| 2139 | step.result_failed_command = try std.zig.allocPrintCmd(gpa, argv, .{ | |
| 2140 | .cwd = switch (child_cwd) { | |
| 2141 | .path => |p| p, | |
| 2142 | .dir => unreachable, | |
| 2143 | .inherit => null, | |
| 2144 | }, | |
| 2145 | .child_env = environ_map, | |
| 2146 | .parent_env = &graph.environ_map, | |
| 2147 | }); | |
| 2148 | ||
| 2149 | try step.handleChildProcUnsupported(maker); | |
| 2150 | ||
| 2151 | var spawn_options: process.SpawnOptions = .{ | |
| 2152 | .argv = argv, | |
| 2153 | .cwd = child_cwd, | |
| 2154 | .environ_map = environ_map, | |
| 2155 | .request_resource_usage_statistics = true, | |
| 2156 | .stdin = if (conf_run.stdin.u != .none) s: { | |
| 2157 | assert(conf_run.flags.stdio != .inherit); | |
| 2158 | break :s .pipe; | |
| 2159 | } else switch (conf_run.flags.stdio) { | |
| 2160 | .infer_from_args => if (has_side_effects) .inherit else .ignore, | |
| 2161 | .inherit => .inherit, | |
| 2162 | .check => .ignore, | |
| 2163 | .zig_test => .pipe, | |
| 2164 | }, | |
| 2165 | .stdout = if (conf_run.captured_stdout.value != null) .pipe else switch (conf_run.flags.stdio) { | |
| 2166 | .infer_from_args => if (has_side_effects) .inherit else .ignore, | |
| 2167 | .inherit => .inherit, | |
| 2168 | .check => if (checksContainStdout(&conf_run)) .pipe else .ignore, | |
| 2169 | .zig_test => .pipe, | |
| 2170 | }, | |
| 2171 | .stderr = if (conf_run.captured_stderr.value != null) .pipe else switch (conf_run.flags.stdio) { | |
| 2172 | .infer_from_args => if (has_side_effects) .inherit else .pipe, | |
| 2173 | .inherit => .inherit, | |
| 2174 | .check => .pipe, | |
| 2175 | .zig_test => .pipe, | |
| 2176 | }, | |
| 2177 | }; | |
| 2178 | ||
| 2179 | if (conf_run.flags.stdio == .zig_test) { | |
| 2180 | const started: Io.Clock.Timestamp = .now(io, .awake); | |
| 2181 | const result = evalZigTest(graph.arena, run, run_index, maker, progress_node, spawn_options, fuzz_context) catch |err| switch (err) { | |
| 2182 | error.Canceled => |e| return e, | |
| 2183 | else => |e| e, | |
| 2184 | }; | |
| 2185 | step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); | |
| 2186 | try result; | |
| 2187 | return null; | |
| 2188 | } else { | |
| 2189 | const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; | |
| 2190 | if (!conf_run.flags.disable_zig_progress and !inherit) { | |
| 2191 | spawn_options.progress_node = progress_node; | |
| 2192 | } | |
| 2193 | const terminal_mode: Io.Terminal.Mode = if (inherit) m: { | |
| 2194 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 2195 | break :m stderr.terminal_mode; | |
| 2196 | } else .no_color; | |
| 2197 | defer if (inherit) io.unlockStderr(); | |
| 2198 | try setColorEnvironmentVariables(&conf_run, environ_map, terminal_mode); | |
| 2199 | ||
| 2200 | const started: Io.Clock.Timestamp = .now(io, .awake); | |
| 2201 | const result = evalGeneric(graph.arena, run_index, maker, spawn_options) catch |err| switch (err) { | |
| 2202 | error.Canceled => |e| return e, | |
| 2203 | else => |e| e, | |
| 2204 | }; | |
| 2205 | step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); | |
| 2206 | return try result; | |
| 2207 | } | |
| 2208 | } | |
| 2209 | ||
| 2210 | fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool { | |
| 2211 | return if (expected) |e| switch (e) { | |
| 2212 | .exited => |expected_code| switch (actual) { | |
| 2213 | .exited => |actual_code| expected_code == actual_code, | |
| 2214 | else => false, | |
| 2215 | }, | |
| 2216 | .signal => |expected_sig| switch (actual) { | |
| 2217 | .signal => |actual_sig| expected_sig == actual_sig, | |
| 2218 | else => false, | |
| 2219 | }, | |
| 2220 | .stopped => |expected_sig| switch (actual) { | |
| 2221 | .stopped => |actual_sig| expected_sig == actual_sig, | |
| 2222 | else => false, | |
| 2223 | }, | |
| 2224 | .unknown => |expected_code| switch (actual) { | |
| 2225 | .unknown => |actual_code| expected_code == actual_code, | |
| 2226 | else => false, | |
| 2227 | }, | |
| 2228 | } else switch (actual) { | |
| 2229 | .exited => true, | |
| 2230 | else => false, | |
| 2231 | }; | |
| 2232 | } | |
| 2233 | ||
| 2234 | fn setColorEnvironmentVariables( | |
| 2235 | conf_run: *const Configuration.Step.Run, | |
| 2236 | environ_map: *EnvMap, | |
| 2237 | terminal_mode: Io.Terminal.Mode, | |
| 2238 | ) !void { | |
| 2239 | color: switch (conf_run.flags.color) { | |
| 2240 | .manual => {}, | |
| 2241 | .enable => { | |
| 2242 | try environ_map.put("CLICOLOR_FORCE", "1"); | |
| 2243 | _ = environ_map.swapRemove("NO_COLOR"); | |
| 2244 | }, | |
| 2245 | .disable => { | |
| 2246 | try environ_map.put("NO_COLOR", "1"); | |
| 2247 | _ = environ_map.swapRemove("CLICOLOR_FORCE"); | |
| 2248 | }, | |
| 2249 | .inherit => switch (terminal_mode) { | |
| 2250 | .no_color, .windows_api => continue :color .disable, | |
| 2251 | .escape_codes => continue :color .enable, | |
| 2252 | }, | |
| 2253 | .auto => { | |
| 2254 | const capture_stderr = conf_run.captured_stderr.value != null or switch (conf_run.flags.stdio) { | |
| 2255 | .check => checksContainStderr(conf_run), | |
| 2256 | .infer_from_args, .inherit, .zig_test => false, | |
| 2257 | }; | |
| 2258 | if (capture_stderr) { | |
| 2259 | continue :color .disable; | |
| 2260 | } else { | |
| 2261 | continue :color .inherit; | |
| 2262 | } | |
| 2263 | }, | |
| 2264 | } | |
| 2265 | } | |
| 2266 | ||
| 2267 | fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool { | |
| 2268 | return conf_run.expect_stdout_exact.value != null or conf_run.expect_stdout_match.slice.len != 0; | |
| 2269 | } | |
| 2270 | ||
| 2271 | fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool { | |
| 2272 | return conf_run.expect_stderr_exact.value != null or conf_run.expect_stderr_match.slice.len != 0; | |
| 2273 | } | |
| 2274 | ||
| 2275 | /// If `path` is cwd-relative, make it relative to the cwd of the child instead. | |
| 2276 | /// | |
| 2277 | /// Whenever a path is included in the argv of a child, it should be put through this function first | |
| 2278 | /// to make sure the child doesn't see paths relative to a cwd other than its own. | |
| 2279 | fn convertPathArg(arena: Allocator, run_index: Configuration.Step.Index, maker: *Maker, path: Path) ![]const u8 { | |
| 2280 | const conf = &maker.scanned_config.configuration; | |
| 2281 | const conf_step = run_index.ptr(conf); | |
| 2282 | const conf_run = conf_step.extended.get(conf.extra).run; | |
| 2283 | const graph = maker.graph; | |
| 2284 | ||
| 2285 | const path_str = try path.toString(arena); | |
| 2286 | if (Dir.path.isAbsolute(path_str)) { | |
| 2287 | // Absolute paths don't need changing. | |
| 2288 | return path_str; | |
| 2289 | } | |
| 2290 | const child_cwd_rel: []const u8 = rel: { | |
| 2291 | const child_lazy_cwd = conf_run.cwd.value orelse break :rel path_str; | |
| 2292 | const child_cwd = try maker.resolveLazyPathIndexAbs(arena, child_lazy_cwd, run_index); | |
| 2293 | // Convert it from relative to *our* cwd, to relative to the *child's* cwd. | |
| 2294 | break :rel try Dir.path.relative(arena, graph.cache.cwd, &graph.environ_map, child_cwd, path_str); | |
| 2295 | }; | |
| 2296 | // Not every path can be made relative, e.g. if the path and the child cwd are on different | |
| 2297 | // disk designators on Windows. In that case, `relative` will return an absolute path which we can | |
| 2298 | // just return. | |
| 2299 | if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel; | |
| 2300 | ||
| 2301 | // We're not done yet. In some cases this path must be prefixed with './': | |
| 2302 | // * On POSIX, the executable name cannot be a single component like 'foo' | |
| 2303 | // * Some executables might treat a leading '-' like a flag, which we must avoid | |
| 2304 | // There's no harm in it, so just *always* apply this prefix. | |
| 2305 | return Dir.path.join(arena, &.{ ".", child_cwd_rel }); | |
| 2306 | } | |
| 2307 | ||
| 2308 | fn addPathForDynLibs( | |
| 2309 | maker: *Maker, | |
| 2310 | arena: Allocator, | |
| 2311 | artifact: Configuration.Step.Index, | |
| 2312 | environ_map: *process.Environ.Map, | |
| 2313 | argv0: []const u8, | |
| 2314 | ) !void { | |
| 2315 | const conf = &maker.scanned_config.configuration; | |
| 2316 | const graph = maker.graph; | |
| 2317 | const use_wine = graph.enable_wine and builtin.os.tag != .windows and std.ascii.endsWithIgnoreCase(argv0, ".exe"); | |
| 2318 | const path_key = if (use_wine) "WINEPATH" else "PATH"; | |
| 2319 | const path_delimiter: u8 = if (builtin.os.tag == .windows or use_wine) | |
| 2320 | Dir.path.delimiter_windows | |
| 2321 | else | |
| 2322 | Dir.path.delimiter; | |
| 2323 | ||
| 2324 | var module_graph: Step.Compile.ModuleGraph = .empty; | |
| 2325 | const compile_deps = try Step.Compile.getCompileDependencies(arena, &module_graph, conf, artifact, true); | |
| 2326 | ||
| 2327 | for (compile_deps) |dep_index| { | |
| 2328 | const conf_comp_step = dep_index.ptr(conf); | |
| 2329 | const conf_comp = conf_comp_step.extended.get(conf.extra).compile; | |
| 2330 | const root_module = conf_comp.root_module.get(conf); | |
| 2331 | const target = root_module.resolved_target.get(conf).?.result.get(conf); | |
| 2332 | if (target.flags.os_tag == .windows and conf_comp.isDynamicLibrary()) { | |
| 2333 | const dll_path = try maker.generatedPath(conf_comp.generated_bin.value.?).toString(arena); | |
| 2334 | const search_path = Dir.path.dirname(dll_path).?; | |
| 2335 | if (environ_map.get(path_key)) |prev_path| { | |
| 2336 | const new_path = try allocPrint(arena, "{s}{c}{s}", .{ prev_path, path_delimiter, search_path }); | |
| 2337 | try environ_map.put(path_key, new_path); | |
| 2338 | } else { | |
| 2339 | try environ_map.put(path_key, search_path); | |
| 2340 | } | |
| 2341 | } | |
| 2342 | } | |
| 2343 | } | |
| 2344 | ||
| 2345 | fn failForeign( | |
| 2346 | arena: Allocator, | |
| 2347 | conf_run: *const Configuration.Step.Run, | |
| 2348 | maker: *Maker, | |
| 2349 | step_index: Configuration.Step.Index, | |
| 2350 | suggested_flag: []const u8, | |
| 2351 | argv0: []const u8, | |
| 2352 | artifact_target: *const std.Target, | |
| 2353 | host_target: *const std.Target, | |
| 2354 | ) Step.ExtendedMakeError { | |
| 2355 | const step = maker.stepByIndex(step_index); | |
| 2356 | switch (conf_run.flags.stdio) { | |
| 2357 | .check, .zig_test => { | |
| 2358 | if (conf_run.flags.skip_foreign_checks) return error.MakeSkipped; | |
| 2359 | ||
| 2360 | const host_name = try host_target.zigTriple(arena); | |
| 2361 | const foreign_name = try artifact_target.zigTriple(arena); | |
| 2362 | ||
| 2363 | return step.fail(maker, | |
| 2364 | \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) | |
| 2365 | \\ consider using {s} or enabling skip_foreign_checks in the Run step | |
| 2366 | , .{ argv0, foreign_name, host_name, suggested_flag }); | |
| 2367 | }, | |
| 2368 | else => { | |
| 2369 | return step.fail(maker, "unable to spawn foreign binary '{s}'", .{argv0}); | |
| 2370 | }, | |
| 2371 | } | |
| 2372 | } |
lib/compiler/Maker/Step/TranslateC.zig created+152| ... | ... | @@ -0,0 +1,152 @@ |
| 1 | const TranslateC = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Configuration = std.Build.Configuration; | |
| 6 | const allocPrint = std.fmt.allocPrint; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | const Step = @import("../Step.zig"); | |
| 10 | const Maker = @import("../../Maker.zig"); | |
| 11 | const PkgConfig = @import("../PkgConfig.zig"); | |
| 12 | ||
| 13 | pub fn make( | |
| 14 | translate_c: *TranslateC, | |
| 15 | step_index: Configuration.Step.Index, | |
| 16 | maker: *Maker, | |
| 17 | progress_node: std.Progress.Node, | |
| 18 | ) Step.ExtendedMakeError!void { | |
| 19 | _ = translate_c; | |
| 20 | const graph = maker.graph; | |
| 21 | const arena = graph.arena; // TODO don't leak into the process arena | |
| 22 | const step = maker.stepByIndex(step_index); | |
| 23 | const conf = &maker.scanned_config.configuration; | |
| 24 | const conf_step = step_index.ptr(conf); | |
| 25 | const conf_tc = conf_step.extended.get(conf.extra).translate_c; | |
| 26 | const cache_root = graph.local_cache_root; | |
| 27 | ||
| 28 | var argv: std.ArrayList([]const u8) = .empty; | |
| 29 | ||
| 30 | try argv.ensureUnusedCapacity(arena, 10); | |
| 31 | argv.appendAssumeCapacity(graph.zig_exe); | |
| 32 | argv.appendAssumeCapacity("translate-c"); | |
| 33 | if (conf_tc.flags.link_libc) { | |
| 34 | argv.appendAssumeCapacity("-lc"); | |
| 35 | } | |
| 36 | ||
| 37 | argv.appendAssumeCapacity("--cache-dir"); | |
| 38 | argv.appendAssumeCapacity(cache_root.path orelse "."); | |
| 39 | ||
| 40 | argv.appendAssumeCapacity("--global-cache-dir"); | |
| 41 | argv.appendAssumeCapacity(graph.global_cache_root.path orelse "."); | |
| 42 | ||
| 43 | if (conf_tc.target.get(conf).?.query.unwrap()) |compact_query| { | |
| 44 | const query = compact_query.get(conf).unwrap(conf); | |
| 45 | argv.appendAssumeCapacity("-target"); | |
| 46 | argv.appendAssumeCapacity(try query.zigTriple(arena)); | |
| 47 | } | |
| 48 | ||
| 49 | switch (conf_tc.flags.optimize) { | |
| 50 | .debug, .default => {}, // Skip since it's the default. | |
| 51 | else => argv.appendAssumeCapacity(try allocPrint(arena, "-O{t}", .{conf_tc.flags.optimize})), | |
| 52 | } | |
| 53 | ||
| 54 | try argv.ensureUnusedCapacity(arena, conf_tc.include_dirs.len * 2); | |
| 55 | for (0..conf_tc.include_dirs.len) |i| | |
| 56 | try Step.Compile.appendIncludeDirFlags(arena, conf_tc.include_dirs.get(conf.extra, i), &argv, step_index, maker); | |
| 57 | ||
| 58 | for (conf_tc.c_macros.slice) |c_macro| { | |
| 59 | (try argv.addManyAsArray(arena, 2)).* = .{ "-D", c_macro.slice(conf) }; | |
| 60 | } | |
| 61 | ||
| 62 | var prev_search_strategy: std.Build.Module.SystemLib.SearchStrategy = .paths_first; | |
| 63 | var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic; | |
| 64 | var seen_system_libs: std.AutoArrayHashMapUnmanaged(Configuration.String, []const []const u8) = .empty; | |
| 65 | ||
| 66 | for (conf_tc.system_libs.slice) |system_lib_index| { | |
| 67 | const system_lib = system_lib_index.get(conf); | |
| 68 | const system_lib_name = system_lib.name.slice(conf); | |
| 69 | const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name); | |
| 70 | if (system_lib_gop.found_existing) { | |
| 71 | try argv.appendSlice(arena, system_lib_gop.value_ptr.*); | |
| 72 | continue; | |
| 73 | } else { | |
| 74 | system_lib_gop.value_ptr.* = &.{}; | |
| 75 | } | |
| 76 | ||
| 77 | if ((system_lib.flags.search_strategy != prev_search_strategy or | |
| 78 | system_lib.flags.preferred_link_mode != prev_preferred_link_mode)) | |
| 79 | { | |
| 80 | try argv.ensureUnusedCapacity(arena, 1); | |
| 81 | switch (system_lib.flags.search_strategy) { | |
| 82 | .no_fallback => switch (system_lib.flags.preferred_link_mode) { | |
| 83 | .dynamic => argv.appendAssumeCapacity("-search_dylibs_only"), | |
| 84 | .static => argv.appendAssumeCapacity("-search_static_only"), | |
| 85 | }, | |
| 86 | .paths_first => switch (system_lib.flags.preferred_link_mode) { | |
| 87 | .dynamic => argv.appendAssumeCapacity("-search_paths_first"), | |
| 88 | .static => argv.appendAssumeCapacity("-search_paths_first_static"), | |
| 89 | }, | |
| 90 | .mode_first => switch (system_lib.flags.preferred_link_mode) { | |
| 91 | .dynamic => argv.appendAssumeCapacity("-search_dylibs_first"), | |
| 92 | .static => argv.appendAssumeCapacity("-search_static_first"), | |
| 93 | }, | |
| 94 | } | |
| 95 | prev_search_strategy = system_lib.flags.search_strategy; | |
| 96 | prev_preferred_link_mode = system_lib.flags.preferred_link_mode; | |
| 97 | } | |
| 98 | ||
| 99 | const prefix: []const u8 = prefix: { | |
| 100 | if (system_lib.flags.needed) break :prefix "-needed-l"; | |
| 101 | if (system_lib.flags.weak) break :prefix "-weak-l"; | |
| 102 | break :prefix "-l"; | |
| 103 | }; | |
| 104 | l: { | |
| 105 | pc: { | |
| 106 | const force = switch (system_lib.flags.use_pkg_config) { | |
| 107 | .no => break :pc, | |
| 108 | .yes => false, | |
| 109 | .force => true, | |
| 110 | }; | |
| 111 | ||
| 112 | const pkg_conf_node = progress_node.start("pkg-config", 0); | |
| 113 | defer pkg_conf_node.end(); | |
| 114 | ||
| 115 | if (PkgConfig.run(maker, step, pkg_conf_node, system_lib_name, force)) |result| { | |
| 116 | try argv.appendSlice(arena, result.cflags); | |
| 117 | try argv.appendSlice(arena, result.libs); | |
| 118 | try seen_system_libs.put(arena, system_lib.name, result.cflags); | |
| 119 | break :l; | |
| 120 | } else |err| switch (err) { | |
| 121 | error.PkgConfigUnavailable, | |
| 122 | error.PackageNotFound, | |
| 123 | => { | |
| 124 | // pkg-config failed, so fall back to linking the library by name directly. | |
| 125 | assert(!force); | |
| 126 | break :pc; | |
| 127 | }, | |
| 128 | else => |e| return e, | |
| 129 | } | |
| 130 | } | |
| 131 | try argv.append(arena, try allocPrint(arena, "{s}{s}", .{ | |
| 132 | prefix, system_lib_name, | |
| 133 | })); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | try argv.ensureUnusedCapacity(arena, 2); | |
| 138 | ||
| 139 | const c_source_path = try maker.resolveLazyPathIndexAbs(arena, conf_tc.src_path, step_index); | |
| 140 | argv.appendAssumeCapacity(c_source_path); | |
| 141 | ||
| 142 | argv.appendAssumeCapacity("--listen=-"); | |
| 143 | const output_dir_path = (Step.evalZigProcess(step_index, maker, argv.items, progress_node, false) catch |err| switch (err) { | |
| 144 | error.NeedCompileErrorCheck => unreachable, | |
| 145 | else => |e| return e, | |
| 146 | }).?; | |
| 147 | ||
| 148 | const stem = Io.Dir.path.stem(Io.Dir.path.basename(c_source_path)); | |
| 149 | const out_basename = try allocPrint(arena, "{s}.zig", .{stem}); | |
| 150 | ||
| 151 | maker.generatedPath(conf_tc.output_file).* = try output_dir_path.join(arena, out_basename); | |
| 152 | } |
lib/compiler/Maker/Step/UpdateSourceFiles.zig created+89| ... | ... | @@ -0,0 +1,89 @@ |
| 1 | const UpdateSourceFiles = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const Path = std.Build.Cache.Path; | |
| 6 | const allocPrint = std.fmt.allocPrint; | |
| 7 | const Configuration = std.Build.Configuration; | |
| 8 | ||
| 9 | const Step = @import("../Step.zig"); | |
| 10 | const Maker = @import("../../Maker.zig"); | |
| 11 | ||
| 12 | pub fn make( | |
| 13 | usf: *UpdateSourceFiles, | |
| 14 | step_index: Configuration.Step.Index, | |
| 15 | maker: *Maker, | |
| 16 | progress_node: std.Progress.Node, | |
| 17 | ) Step.ExtendedMakeError!void { | |
| 18 | _ = usf; | |
| 19 | const graph = maker.graph; | |
| 20 | const arena = maker.graph.arena; // TODO don't leak into process arena | |
| 21 | const io = graph.io; | |
| 22 | const step = maker.stepByIndex(step_index); | |
| 23 | const conf = &maker.scanned_config.configuration; | |
| 24 | const conf_step = step_index.ptr(conf); | |
| 25 | const conf_usf = conf_step.extended.get(conf.extra).update_source_files; | |
| 26 | const build_root = graph.build_root_directory; | |
| 27 | ||
| 28 | if (conf_step.owner != .root) | |
| 29 | return step.fail(maker, "non-root package attempted to update its source files", .{}); | |
| 30 | ||
| 31 | var any_miss = false; | |
| 32 | ||
| 33 | progress_node.setEstimatedTotalItems(conf_usf.embeds.slice.len + conf_usf.copies.slice.len); | |
| 34 | ||
| 35 | step.clearWatchInputs(maker); | |
| 36 | ||
| 37 | for (conf_usf.embeds.slice) |*embed| { | |
| 38 | const dest_path: Path = .{ | |
| 39 | .root_dir = build_root, | |
| 40 | .sub_path = embed.sub_path.slice(conf), | |
| 41 | }; | |
| 42 | if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| { | |
| 43 | const dirname_path: Path = .{ | |
| 44 | .root_dir = build_root, | |
| 45 | .sub_path = dirname, | |
| 46 | }; | |
| 47 | dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| | |
| 48 | return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err }); | |
| 49 | } | |
| 50 | dest_path.root_dir.handle.writeFile(io, .{ | |
| 51 | .sub_path = dest_path.sub_path, | |
| 52 | .data = embed.contents.slice(conf), | |
| 53 | }) catch |err| return step.fail(maker, "failed writing file {f}: {t}", .{ dest_path, err }); | |
| 54 | any_miss = true; | |
| 55 | progress_node.completeOne(); | |
| 56 | } | |
| 57 | ||
| 58 | for (conf_usf.copies.slice) |*copy| { | |
| 59 | const dest_path: Path = .{ | |
| 60 | .root_dir = build_root, | |
| 61 | .sub_path = copy.sub_path.slice(conf), | |
| 62 | }; | |
| 63 | if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| { | |
| 64 | const dirname_path: Path = .{ | |
| 65 | .root_dir = build_root, | |
| 66 | .sub_path = dirname, | |
| 67 | }; | |
| 68 | dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| | |
| 69 | return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err }); | |
| 70 | } | |
| 71 | const src_lazy_path = copy.src_file.get(conf); | |
| 72 | const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index); | |
| 73 | try step.addWatchInput(maker, arena, src_lazy_path); | |
| 74 | ||
| 75 | const prev_status = source_path.root_dir.handle.updateFile( | |
| 76 | io, | |
| 77 | source_path.sub_path, | |
| 78 | dest_path.root_dir.handle, | |
| 79 | dest_path.sub_path, | |
| 80 | .{}, | |
| 81 | ) catch |err| return step.fail(maker, "failed updating file from {f} to {f}: {t}", .{ | |
| 82 | source_path, dest_path, err, | |
| 83 | }); | |
| 84 | any_miss = any_miss or prev_status == .stale; | |
| 85 | progress_node.completeOne(); | |
| 86 | } | |
| 87 | ||
| 88 | step.result_cached = !any_miss; | |
| 89 | } |
lib/compiler/Maker/Step/WriteFile.zig created+294| ... | ... | @@ -0,0 +1,294 @@ |
| 1 | const WriteFile = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Io = std.Io; | |
| 5 | const assert = std.debug.assert; | |
| 6 | const Path = std.Build.Cache.Path; | |
| 7 | const allocPrint = std.fmt.allocPrint; | |
| 8 | const Configuration = std.Build.Configuration; | |
| 9 | ||
| 10 | const Step = @import("../Step.zig"); | |
| 11 | const Maker = @import("../../Maker.zig"); | |
| 12 | ||
| 13 | pub fn make( | |
| 14 | wf: *WriteFile, | |
| 15 | step_index: Configuration.Step.Index, | |
| 16 | maker: *Maker, | |
| 17 | progress_node: std.Progress.Node, | |
| 18 | ) Step.ExtendedMakeError!void { | |
| 19 | _ = wf; | |
| 20 | const graph = maker.graph; | |
| 21 | const gpa = maker.gpa; | |
| 22 | const arena = maker.graph.arena; // TODO don't leak into process arena | |
| 23 | const io = graph.io; | |
| 24 | const step = maker.stepByIndex(step_index); | |
| 25 | const conf = &maker.scanned_config.configuration; | |
| 26 | const conf_step = step_index.ptr(conf); | |
| 27 | const conf_wf = conf_step.extended.get(conf.extra).write_file; | |
| 28 | const cache_root = graph.local_cache_root; | |
| 29 | const directories = conf_wf.directories.slice; | |
| 30 | ||
| 31 | const open_dir_cache = try arena.alloc(Io.Dir, directories.len); | |
| 32 | var open_dirs_count: u32 = 0; | |
| 33 | defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]); | |
| 34 | ||
| 35 | // Doesn't yet include contents of directories. | |
| 36 | var total_items: usize = conf_wf.embeds.slice.len + conf_wf.copies.slice.len + conf_wf.directories.slice.len; | |
| 37 | progress_node.setEstimatedTotalItems(total_items); | |
| 38 | ||
| 39 | switch (conf_wf.flags.mode) { | |
| 40 | .whole_cached => { | |
| 41 | step.clearWatchInputs(maker); | |
| 42 | ||
| 43 | // The cache is used here primarily as a way to find a canonical | |
| 44 | // location to put build artifacts without parallel step execution | |
| 45 | // clobbering each other. | |
| 46 | ||
| 47 | var man = graph.cache.obtain(); | |
| 48 | defer man.deinit(); | |
| 49 | ||
| 50 | for (conf_wf.embeds.slice) |*embed| { | |
| 51 | man.hash.addBytes(embed.sub_path.slice(conf)); | |
| 52 | man.hash.addBytes(embed.contents.slice(conf)); | |
| 53 | } | |
| 54 | ||
| 55 | for (conf_wf.copies.slice) |*copy| { | |
| 56 | man.hash.addBytes(copy.sub_path.slice(conf)); | |
| 57 | const src_lazy_path = copy.src_file.get(conf); | |
| 58 | const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index); | |
| 59 | _ = try man.addFilePath(source_path, null); | |
| 60 | try step.addWatchInput(maker, arena, src_lazy_path); | |
| 61 | } | |
| 62 | ||
| 63 | for (directories, open_dir_cache) |conf_dir, *opened_dir| { | |
| 64 | const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{}; | |
| 65 | const include_extensions = conf_dir.include_extensions.slice(conf); | |
| 66 | ||
| 67 | man.hash.addBytes(conf_dir.sub_path.slice(conf)); | |
| 68 | for (exclude_extensions) |ext| man.hash.addBytes(ext.slice(conf)); | |
| 69 | if (include_extensions) |includes| for (includes) |inc| { | |
| 70 | man.hash.addBytes(inc.slice(conf)); | |
| 71 | }; | |
| 72 | ||
| 73 | const src_lazy_path = conf_dir.src_path.get(conf); | |
| 74 | const need_derived_inputs = try step.addDirectoryWatchInput(maker, src_lazy_path); | |
| 75 | const src_dir_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index); | |
| 76 | ||
| 77 | var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { | |
| 78 | return step.fail(maker, "failed opening source directory {f}: {t}", .{ src_dir_path, err }); | |
| 79 | }; | |
| 80 | opened_dir.* = src_dir; | |
| 81 | open_dirs_count += 1; | |
| 82 | ||
| 83 | var it = try src_dir.walk(gpa); | |
| 84 | defer it.deinit(); | |
| 85 | while (it.next(io) catch |err| switch (err) { | |
| 86 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 87 | else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }), | |
| 88 | }) |entry| { | |
| 89 | if (!pathIncluded(conf, exclude_extensions, include_extensions, entry.path)) continue; | |
| 90 | ||
| 91 | switch (entry.kind) { | |
| 92 | .directory => { | |
| 93 | if (need_derived_inputs) { | |
| 94 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 95 | try step.addDirectoryWatchInputFromPath(maker, entry_path); | |
| 96 | } | |
| 97 | }, | |
| 98 | .file => { | |
| 99 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 100 | _ = try man.addFilePath(entry_path, null); | |
| 101 | total_items += 1; | |
| 102 | }, | |
| 103 | else => continue, | |
| 104 | } | |
| 105 | } | |
| 106 | } | |
| 107 | ||
| 108 | if (try step.cacheHit(maker, &man)) { | |
| 109 | const digest = man.final(); | |
| 110 | maker.generatedPath(conf_wf.generated_directory).* = .{ | |
| 111 | .root_dir = cache_root, | |
| 112 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), | |
| 113 | }; | |
| 114 | assert(step.result_cached); | |
| 115 | return; | |
| 116 | } | |
| 117 | ||
| 118 | const digest = man.final(); | |
| 119 | const out_path: Path = .{ | |
| 120 | .root_dir = cache_root, | |
| 121 | .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }), | |
| 122 | }; | |
| 123 | ||
| 124 | progress_node.setEstimatedTotalItems(total_items); | |
| 125 | try operate(maker, step_index, open_dir_cache, out_path, progress_node); | |
| 126 | try step.writeManifest(maker, &man); | |
| 127 | ||
| 128 | maker.generatedPath(conf_wf.generated_directory).* = out_path; | |
| 129 | }, | |
| 130 | .tmp => { | |
| 131 | step.result_cached = false; | |
| 132 | ||
| 133 | var rand_int: u64 = undefined; | |
| 134 | io.random(@ptrCast(&rand_int)); | |
| 135 | const hex_digest = std.fmt.hex(rand_int); | |
| 136 | ||
| 137 | const out_path: Path = .{ | |
| 138 | .root_dir = cache_root, | |
| 139 | .sub_path = try Io.Dir.path.join(arena, &.{ "tmp", &hex_digest }), | |
| 140 | }; | |
| 141 | ||
| 142 | try operate(maker, step_index, open_dir_cache, out_path, progress_node); | |
| 143 | ||
| 144 | maker.generatedPath(conf_wf.generated_directory).* = out_path; | |
| 145 | }, | |
| 146 | .mutate => { | |
| 147 | step.result_cached = false; | |
| 148 | const root_path = try maker.resolveLazyPathIndex(arena, conf_wf.mutate_path.value.?, step_index); | |
| 149 | try operate(maker, step_index, open_dir_cache, root_path, progress_node); | |
| 150 | maker.generatedPath(conf_wf.generated_directory).* = root_path; | |
| 151 | }, | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | fn operate( | |
| 156 | maker: *Maker, | |
| 157 | step_index: Configuration.Step.Index, | |
| 158 | open_dir_cache: []const Io.Dir, | |
| 159 | root_path: std.Build.Cache.Path, | |
| 160 | progress_node: std.Progress.Node, | |
| 161 | ) !void { | |
| 162 | const graph = maker.graph; | |
| 163 | const gpa = maker.gpa; | |
| 164 | const arena = maker.graph.arena; // TODO don't leak into process arena | |
| 165 | const io = graph.io; | |
| 166 | const step = maker.stepByIndex(step_index); | |
| 167 | const conf = &maker.scanned_config.configuration; | |
| 168 | const conf_step = step_index.ptr(conf); | |
| 169 | const conf_wf = conf_step.extended.get(conf.extra).write_file; | |
| 170 | ||
| 171 | const root_directory: std.Build.Cache.Directory = .{ | |
| 172 | .handle = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err| | |
| 173 | return step.fail(maker, "failed creating path {f}: {t}", .{ root_path, err }), | |
| 174 | .path = try root_path.toString(arena), | |
| 175 | }; | |
| 176 | defer root_directory.handle.close(io); | |
| 177 | ||
| 178 | for (conf_wf.embeds.slice) |*embed| { | |
| 179 | const dest_path: Path = .{ | |
| 180 | .root_dir = root_directory, | |
| 181 | .sub_path = embed.sub_path.slice(conf), | |
| 182 | }; | |
| 183 | if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| { | |
| 184 | const dirname_path: Path = .{ | |
| 185 | .root_dir = root_directory, | |
| 186 | .sub_path = dirname, | |
| 187 | }; | |
| 188 | dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| | |
| 189 | return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err }); | |
| 190 | } | |
| 191 | dest_path.root_dir.handle.writeFile(io, .{ | |
| 192 | .sub_path = dest_path.sub_path, | |
| 193 | .data = embed.contents.slice(conf), | |
| 194 | }) catch |err| return step.fail(maker, "failed writing contents to file {f}: {t}", .{ dest_path, err }); | |
| 195 | progress_node.completeOne(); | |
| 196 | } | |
| 197 | ||
| 198 | for (conf_wf.copies.slice) |*copy| { | |
| 199 | const dest_path: Path = .{ | |
| 200 | .root_dir = root_directory, | |
| 201 | .sub_path = copy.sub_path.slice(conf), | |
| 202 | }; | |
| 203 | // Rather than passing make_path = true below, this optimizes for the | |
| 204 | // more common case where the directory does not exist. | |
| 205 | if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| { | |
| 206 | const dirname_path: Path = .{ | |
| 207 | .root_dir = root_directory, | |
| 208 | .sub_path = dirname, | |
| 209 | }; | |
| 210 | dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err| | |
| 211 | return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err }); | |
| 212 | } | |
| 213 | const source_path = try maker.resolveLazyPathIndex(arena, copy.src_file, step_index); | |
| 214 | Io.Dir.copyFile( | |
| 215 | source_path.root_dir.handle, | |
| 216 | source_path.sub_path, | |
| 217 | dest_path.root_dir.handle, | |
| 218 | dest_path.sub_path, | |
| 219 | io, | |
| 220 | .{}, | |
| 221 | ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{ | |
| 222 | source_path, dest_path, err, | |
| 223 | }); | |
| 224 | progress_node.completeOne(); | |
| 225 | } | |
| 226 | ||
| 227 | for (conf_wf.directories.slice, open_dir_cache) |conf_dir, already_open_dir| { | |
| 228 | const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{}; | |
| 229 | const include_extensions = conf_dir.include_extensions.slice(conf); | |
| 230 | ||
| 231 | const src_dir_path = try maker.resolveLazyPathIndex(arena, conf_dir.src_path, step_index); | |
| 232 | const dest_dir_path: Path = .{ | |
| 233 | .root_dir = root_directory, | |
| 234 | .sub_path = conf_dir.sub_path.slice(conf), | |
| 235 | }; | |
| 236 | ||
| 237 | if (dest_dir_path.sub_path.len != 0) { | |
| 238 | dest_dir_path.root_dir.handle.createDirPath(io, dest_dir_path.sub_path) catch |err| | |
| 239 | return step.fail(maker, "failed creating path {f}: {t}", .{ dest_dir_path, err }); | |
| 240 | } | |
| 241 | ||
| 242 | var it = try already_open_dir.walk(gpa); | |
| 243 | defer it.deinit(); | |
| 244 | while (it.next(io) catch |err| switch (err) { | |
| 245 | error.Canceled, error.OutOfMemory => |e| return e, | |
| 246 | else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }), | |
| 247 | }) |entry| { | |
| 248 | if (!pathIncluded(conf, exclude_extensions, include_extensions, entry.path)) continue; | |
| 249 | ||
| 250 | const src_entry_path = try src_dir_path.join(arena, entry.path); | |
| 251 | const dest_path = try dest_dir_path.join(arena, entry.path); | |
| 252 | switch (entry.kind) { | |
| 253 | .directory => dest_path.root_dir.handle.createDirPath(io, dest_path.sub_path) catch |err| { | |
| 254 | return step.fail(maker, "failed creating path {f}: {t}", .{ dest_path, err }); | |
| 255 | }, | |
| 256 | .file => { | |
| 257 | Io.Dir.copyFile( | |
| 258 | src_entry_path.root_dir.handle, | |
| 259 | src_entry_path.sub_path, | |
| 260 | dest_path.root_dir.handle, | |
| 261 | dest_path.sub_path, | |
| 262 | io, | |
| 263 | .{ .make_path = true }, // Directory entry may be filtered out above. | |
| 264 | ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{ | |
| 265 | src_entry_path, dest_path, err, | |
| 266 | }); | |
| 267 | progress_node.completeOne(); | |
| 268 | }, | |
| 269 | else => continue, | |
| 270 | } | |
| 271 | } | |
| 272 | } | |
| 273 | } | |
| 274 | ||
| 275 | fn pathIncluded( | |
| 276 | conf: *const Configuration, | |
| 277 | exclude_extensions: []const Configuration.String, | |
| 278 | include_extensions: ?[]const Configuration.String, | |
| 279 | path: []const u8, | |
| 280 | ) bool { | |
| 281 | for (exclude_extensions) |ext| { | |
| 282 | if (std.mem.endsWith(u8, path, ext.slice(conf))) | |
| 283 | return false; | |
| 284 | } | |
| 285 | if (include_extensions) |incs| { | |
| 286 | for (incs) |inc| { | |
| 287 | if (std.mem.endsWith(u8, path, inc.slice(conf))) | |
| 288 | return true; | |
| 289 | } else { | |
| 290 | return false; | |
| 291 | } | |
| 292 | } | |
| 293 | return true; | |
| 294 | } |
lib/compiler/Maker/Watch.zig created+989| ... | ... | @@ -0,0 +1,989 @@ |
| 1 | const Watch = @This(); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 6 | const Allocator = std.mem.Allocator; | |
| 7 | const assert = std.debug.assert; | |
| 8 | const fatal = std.process.fatal; | |
| 9 | const Configuration = std.Build.Configuration; | |
| 10 | ||
| 11 | const FsEvents = @import("Watch/FsEvents.zig"); | |
| 12 | const Step = @import("Step.zig"); | |
| 13 | const Maker = @import("../Maker.zig"); | |
| 14 | ||
| 15 | os: Os, | |
| 16 | /// The number to show as the number of directories being watched. | |
| 17 | dir_count: usize, | |
| 18 | // These fields are common to most implementations so are kept here for simplicity. | |
| 19 | // They are `undefined` on implementations which do not utilize then. | |
| 20 | dir_table: DirTable, | |
| 21 | generation: Generation, | |
| 22 | maker: *Maker, | |
| 23 | ||
| 24 | pub const have_impl = Os != void; | |
| 25 | ||
| 26 | /// Key is the directory to watch which contains one or more files we are | |
| 27 | /// interested in noticing changes to. | |
| 28 | /// | |
| 29 | /// Value is generation. | |
| 30 | const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false); | |
| 31 | ||
| 32 | /// Special key of "." means any changes in this directory trigger the steps. | |
| 33 | const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet); | |
| 34 | const StepSet = std.AutoArrayHashMapUnmanaged(Configuration.Step.Index, Generation); | |
| 35 | ||
| 36 | const Generation = u8; | |
| 37 | ||
| 38 | const Hash = std.hash.Wyhash; | |
| 39 | const Cache = std.Build.Cache; | |
| 40 | ||
| 41 | const Os = switch (builtin.os.tag) { | |
| 42 | .linux => struct { | |
| 43 | const posix = std.posix; | |
| 44 | ||
| 45 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. | |
| 46 | handle_table: HandleTable, | |
| 47 | /// fanotify file descriptors are keyed by mount id since marks | |
| 48 | /// are limited to a single filesystem. | |
| 49 | poll_fds: std.AutoArrayHashMapUnmanaged(MountId, posix.pollfd), | |
| 50 | ||
| 51 | const MountId = i32; | |
| 52 | const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, struct { mount_id: MountId, reaction_set: ReactionSet }, FileHandle.Adapter, false); | |
| 53 | ||
| 54 | const fan_mask: std.os.linux.fanotify.MarkMask = .{ | |
| 55 | .CLOSE_WRITE = true, | |
| 56 | .CREATE = true, | |
| 57 | .DELETE = true, | |
| 58 | .DELETE_SELF = true, | |
| 59 | .EVENT_ON_CHILD = true, | |
| 60 | .MOVED_FROM = true, | |
| 61 | .MOVED_TO = true, | |
| 62 | .MOVE_SELF = true, | |
| 63 | .ONDIR = true, | |
| 64 | }; | |
| 65 | ||
| 66 | const FileHandle = struct { | |
| 67 | handle: *align(1) std.os.linux.file_handle, | |
| 68 | ||
| 69 | fn clone(lfh: FileHandle, gpa: Allocator) Allocator.Error!FileHandle { | |
| 70 | const bytes = lfh.slice(); | |
| 71 | const new_ptr = try gpa.alignedAlloc( | |
| 72 | u8, | |
| 73 | .of(std.os.linux.file_handle), | |
| 74 | @sizeOf(std.os.linux.file_handle) + bytes.len, | |
| 75 | ); | |
| 76 | const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr); | |
| 77 | new_header.* = lfh.handle.*; | |
| 78 | const new: FileHandle = .{ .handle = new_header }; | |
| 79 | @memcpy(new.slice(), lfh.slice()); | |
| 80 | return new; | |
| 81 | } | |
| 82 | ||
| 83 | fn destroy(lfh: FileHandle, gpa: Allocator) void { | |
| 84 | const ptr: [*]u8 = @ptrCast(lfh.handle); | |
| 85 | const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes]; | |
| 86 | return gpa.free(allocated_slice); | |
| 87 | } | |
| 88 | ||
| 89 | fn slice(lfh: FileHandle) []u8 { | |
| 90 | const ptr: [*]u8 = &lfh.handle.f_handle; | |
| 91 | return ptr[0..lfh.handle.handle_bytes]; | |
| 92 | } | |
| 93 | ||
| 94 | const Adapter = struct { | |
| 95 | pub fn hash(self: Adapter, a: FileHandle) u32 { | |
| 96 | _ = self; | |
| 97 | const unsigned_type: u32 = @bitCast(a.handle.handle_type); | |
| 98 | return @truncate(Hash.hash(unsigned_type, a.slice())); | |
| 99 | } | |
| 100 | pub fn eql(self: Adapter, a: FileHandle, b: FileHandle, b_index: usize) bool { | |
| 101 | _ = self; | |
| 102 | _ = b_index; | |
| 103 | return a.handle.handle_type == b.handle.handle_type and std.mem.eql(u8, a.slice(), b.slice()); | |
| 104 | } | |
| 105 | }; | |
| 106 | }; | |
| 107 | ||
| 108 | fn init(maker: *Maker) !Watch { | |
| 109 | return .{ | |
| 110 | .dir_table = .{}, | |
| 111 | .dir_count = 0, | |
| 112 | .os = switch (builtin.os.tag) { | |
| 113 | .linux => .{ | |
| 114 | .handle_table = .{}, | |
| 115 | .poll_fds = .{}, | |
| 116 | }, | |
| 117 | else => {}, | |
| 118 | }, | |
| 119 | .generation = 0, | |
| 120 | .maker = maker, | |
| 121 | }; | |
| 122 | } | |
| 123 | ||
| 124 | fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle { | |
| 125 | var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined; | |
| 126 | var buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 127 | const adjusted_path = if (path.sub_path.len == 0) "./" else std.fmt.bufPrint(&buf, "{s}/", .{ | |
| 128 | path.sub_path, | |
| 129 | }) catch return error.NameTooLong; | |
| 130 | const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer); | |
| 131 | stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle); | |
| 132 | try posix.name_to_handle_at(path.root_dir.handle.handle, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID); | |
| 133 | const stack_lfh: FileHandle = .{ .handle = stack_ptr }; | |
| 134 | return stack_lfh.clone(gpa); | |
| 135 | } | |
| 136 | ||
| 137 | fn markDirtySteps(w: *Watch, fan_fd: posix.fd_t) !bool { | |
| 138 | const maker = w.maker; | |
| 139 | const fanotify = std.os.linux.fanotify; | |
| 140 | const M = fanotify.event_metadata; | |
| 141 | var events_buf: [256 + 4096]u8 = undefined; | |
| 142 | var any_dirty = false; | |
| 143 | while (true) { | |
| 144 | var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) { | |
| 145 | error.WouldBlock => return any_dirty, | |
| 146 | else => |e| return e, | |
| 147 | }; | |
| 148 | var meta: [*]align(1) M = @ptrCast(&events_buf); | |
| 149 | while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ | |
| 150 | len -= meta[0].event_len; | |
| 151 | meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); | |
| 152 | }) { | |
| 153 | assert(meta[0].vers == M.VERSION); | |
| 154 | if (meta[0].mask.Q_OVERFLOW) { | |
| 155 | any_dirty = true; | |
| 156 | std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); | |
| 157 | markAllFilesDirty(w); | |
| 158 | return true; | |
| 159 | } | |
| 160 | const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); | |
| 161 | switch (fid.hdr.info_type) { | |
| 162 | .DFID_NAME => { | |
| 163 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); | |
| 164 | const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); | |
| 165 | const file_name = std.mem.span(file_name_z); | |
| 166 | const lfh: FileHandle = .{ .handle = file_handle }; | |
| 167 | if (w.os.handle_table.getPtr(lfh)) |value| { | |
| 168 | if (value.reaction_set.getPtr(".")) |glob_set| | |
| 169 | any_dirty = markStepSetDirty(maker, glob_set, any_dirty); | |
| 170 | if (value.reaction_set.getPtr(file_name)) |step_set| | |
| 171 | any_dirty = markStepSetDirty(maker, step_set, any_dirty); | |
| 172 | } | |
| 173 | }, | |
| 174 | else => |t| std.log.warn("unexpected fanotify event '{t}'", .{t}), | |
| 175 | } | |
| 176 | } | |
| 177 | } | |
| 178 | } | |
| 179 | ||
| 180 | fn update(w: *Watch, steps: []const Configuration.Step.Index) !void { | |
| 181 | const maker = w.maker; | |
| 182 | const gpa = maker.gpa; | |
| 183 | ||
| 184 | // Add missing marks and note persisted ones. | |
| 185 | for (steps) |step_index| { | |
| 186 | const step = maker.stepByIndex(step_index); | |
| 187 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | |
| 188 | const reaction_set = rs: { | |
| 189 | const gop = try w.dir_table.getOrPut(gpa, path); | |
| 190 | if (!gop.found_existing) { | |
| 191 | var mount_id: MountId = undefined; | |
| 192 | const dir_handle = getDirHandle(gpa, path, &mount_id) catch |err| switch (err) { | |
| 193 | error.FileNotFound => { | |
| 194 | std.debug.assert(w.dir_table.swapRemove(path)); | |
| 195 | continue; | |
| 196 | }, | |
| 197 | else => return err, | |
| 198 | }; | |
| 199 | const fan_fd = blk: { | |
| 200 | const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id); | |
| 201 | if (!fd_gop.found_existing) { | |
| 202 | const fan_fd = std.posix.fanotify_init(.{ | |
| 203 | .CLASS = .NOTIF, | |
| 204 | .CLOEXEC = true, | |
| 205 | .NONBLOCK = true, | |
| 206 | .REPORT_NAME = true, | |
| 207 | .REPORT_DIR_FID = true, | |
| 208 | .REPORT_FID = true, | |
| 209 | .REPORT_TARGET_FID = true, | |
| 210 | }, 0) catch |err| switch (err) { | |
| 211 | error.UnsupportedFlags => fatal("fanotify_init failed due to old kernel; requires 5.17+", .{}), | |
| 212 | else => |e| return e, | |
| 213 | }; | |
| 214 | fd_gop.value_ptr.* = .{ | |
| 215 | .fd = fan_fd, | |
| 216 | .events = std.posix.POLL.IN, | |
| 217 | .revents = undefined, | |
| 218 | }; | |
| 219 | } | |
| 220 | break :blk fd_gop.value_ptr.*.fd; | |
| 221 | }; | |
| 222 | // `dir_handle` may already be present in the table in | |
| 223 | // the case that we have multiple Cache.Path instances | |
| 224 | // that compare inequal but ultimately point to the same | |
| 225 | // directory on the file system. | |
| 226 | // In such case, we must revert adding this directory, but keep | |
| 227 | // the additions to the step set. | |
| 228 | const dh_gop = try w.os.handle_table.getOrPut(gpa, dir_handle); | |
| 229 | if (dh_gop.found_existing) { | |
| 230 | _ = w.dir_table.pop(); | |
| 231 | } else { | |
| 232 | assert(dh_gop.index == gop.index); | |
| 233 | dh_gop.value_ptr.* = .{ .mount_id = mount_id, .reaction_set = .{} }; | |
| 234 | posix.fanotify_mark(fan_fd, .{ | |
| 235 | .ADD = true, | |
| 236 | .ONLYDIR = true, | |
| 237 | }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| | |
| 238 | fatal("unable to watch {f}: {t}", .{ path, err }); | |
| 239 | } | |
| 240 | break :rs &dh_gop.value_ptr.reaction_set; | |
| 241 | } | |
| 242 | break :rs &w.os.handle_table.values()[gop.index].reaction_set; | |
| 243 | }; | |
| 244 | for (files.items) |basename| { | |
| 245 | const gop = try reaction_set.getOrPut(gpa, basename); | |
| 246 | if (!gop.found_existing) gop.value_ptr.* = .{}; | |
| 247 | try gop.value_ptr.put(gpa, step_index, w.generation); | |
| 248 | } | |
| 249 | } | |
| 250 | } | |
| 251 | ||
| 252 | { | |
| 253 | // Remove marks for files that are no longer inputs. | |
| 254 | var i: usize = 0; | |
| 255 | while (i < w.os.handle_table.entries.len) { | |
| 256 | { | |
| 257 | const reaction_set = &w.os.handle_table.values()[i].reaction_set; | |
| 258 | var step_set_i: usize = 0; | |
| 259 | while (step_set_i < reaction_set.entries.len) { | |
| 260 | const step_set = &reaction_set.values()[step_set_i]; | |
| 261 | var dirent_i: usize = 0; | |
| 262 | while (dirent_i < step_set.entries.len) { | |
| 263 | const generations = step_set.values(); | |
| 264 | if (generations[dirent_i] == w.generation) { | |
| 265 | dirent_i += 1; | |
| 266 | continue; | |
| 267 | } | |
| 268 | step_set.swapRemoveAt(dirent_i); | |
| 269 | } | |
| 270 | if (step_set.entries.len > 0) { | |
| 271 | step_set_i += 1; | |
| 272 | continue; | |
| 273 | } | |
| 274 | reaction_set.swapRemoveAt(step_set_i); | |
| 275 | } | |
| 276 | if (reaction_set.entries.len > 0) { | |
| 277 | i += 1; | |
| 278 | continue; | |
| 279 | } | |
| 280 | } | |
| 281 | ||
| 282 | const path = w.dir_table.keys()[i]; | |
| 283 | ||
| 284 | const mount_id = w.os.handle_table.values()[i].mount_id; | |
| 285 | const fan_fd = w.os.poll_fds.getEntry(mount_id).?.value_ptr.fd; | |
| 286 | posix.fanotify_mark(fan_fd, .{ | |
| 287 | .REMOVE = true, | |
| 288 | .ONLYDIR = true, | |
| 289 | }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| switch (err) { | |
| 290 | error.FileNotFound => {}, // Expected, harmless. | |
| 291 | else => |e| std.log.warn("unable to unwatch {f}: {t}", .{ path, e }), | |
| 292 | }; | |
| 293 | ||
| 294 | w.dir_table.swapRemoveAt(i); | |
| 295 | w.os.handle_table.swapRemoveAt(i); | |
| 296 | } | |
| 297 | w.generation +%= 1; | |
| 298 | } | |
| 299 | w.dir_count = w.dir_table.count(); | |
| 300 | } | |
| 301 | ||
| 302 | fn wait(w: *Watch, timeout: Timeout) !WaitResult { | |
| 303 | const events_len = try std.posix.poll(w.os.poll_fds.values(), timeout.to_i32_ms()); | |
| 304 | if (events_len == 0) | |
| 305 | return .timeout; | |
| 306 | for (w.os.poll_fds.values()) |poll_fd| { | |
| 307 | if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and try markDirtySteps(w, poll_fd.fd)) | |
| 308 | return .dirty; | |
| 309 | } | |
| 310 | return .clean; | |
| 311 | } | |
| 312 | }, | |
| 313 | .windows => struct { | |
| 314 | const windows = std.os.windows; | |
| 315 | ||
| 316 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. | |
| 317 | handle_table: std.ArrayHashMapUnmanaged(*Directory, void, Directory.TableAdapter, false), | |
| 318 | ready_dirs: std.DoublyLinkedList, | |
| 319 | ||
| 320 | const FileId = struct { | |
| 321 | volumeSerialNumber: windows.ULONG, | |
| 322 | indexNumber: windows.LARGE_INTEGER, | |
| 323 | }; | |
| 324 | ||
| 325 | const Directory = struct { | |
| 326 | reaction_set: ReactionSet, | |
| 327 | id: FileId, | |
| 328 | file: Io.File, | |
| 329 | state: enum { idle, listening, ready }, | |
| 330 | iosb: windows.IO_STATUS_BLOCK, | |
| 331 | // 64 KB is the packet size limit when monitoring over a network. | |
| 332 | // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks | |
| 333 | buffer: [64 * 1024]u8 align(@alignOf(windows.FILE.NOTIFY.INFORMATION)), | |
| 334 | ready_node: std.DoublyLinkedList.Node, | |
| 335 | ||
| 336 | /// Start listening for events, buffer field will be overwritten eventually. | |
| 337 | fn startListening(dir: *Directory, w: *Watch) !void { | |
| 338 | assert(dir.file.flags.nonblocking); | |
| 339 | assert(dir.state == .idle); | |
| 340 | switch (windows.ntdll.NtNotifyChangeDirectoryFileEx( | |
| 341 | dir.file.handle, | |
| 342 | null, | |
| 343 | &notifyApc, | |
| 344 | w, | |
| 345 | &dir.iosb, | |
| 346 | &dir.buffer, | |
| 347 | dir.buffer.len, | |
| 348 | .{ | |
| 349 | .FILE_NAME = true, | |
| 350 | .DIR_NAME = true, | |
| 351 | .SIZE = true, | |
| 352 | .LAST_WRITE = true, | |
| 353 | .CREATION = true, | |
| 354 | }, | |
| 355 | .FALSE, | |
| 356 | .Notify, | |
| 357 | )) { | |
| 358 | .SUCCESS, .PENDING => dir.state = .listening, | |
| 359 | .ILLEGAL_FUNCTION => return error.ReadDirectoryChangesUnsupported, | |
| 360 | else => |status| return windows.unexpectedStatus(status), | |
| 361 | } | |
| 362 | } | |
| 363 | ||
| 364 | fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(std.Io.Threaded.apc_align) callconv(.winapi) void { | |
| 365 | const w: *Watch = @ptrCast(@alignCast(apc_context)); | |
| 366 | const dir: *Directory = @fieldParentPtr("iosb", iosb); | |
| 367 | assert(iosb.u.Status != .PENDING); | |
| 368 | assert(dir.state == .listening); | |
| 369 | w.os.ready_dirs.append(&dir.ready_node); | |
| 370 | dir.state = .ready; | |
| 371 | } | |
| 372 | ||
| 373 | fn init(gpa: Allocator, path: Cache.Path) !*Directory { | |
| 374 | // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW) | |
| 375 | // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW. | |
| 376 | var dir_handle: windows.HANDLE = undefined; | |
| 377 | const root_fd = path.root_dir.handle.handle; | |
| 378 | const sub_path = path.subPathOrDot(); | |
| 379 | const sub_path_w = try Io.Threaded.sliceToPrefixedFileW(root_fd, sub_path, .{}); // TODO eliminate this call | |
| 380 | var iosb: windows.IO_STATUS_BLOCK = undefined; | |
| 381 | switch (windows.ntdll.NtCreateFile( | |
| 382 | &dir_handle, | |
| 383 | .{ | |
| 384 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ | |
| 385 | .LIST = true, | |
| 386 | } }, | |
| 387 | .STANDARD = .{ .SYNCHRONIZE = true }, | |
| 388 | .GENERIC = .{ .READ = true }, | |
| 389 | }, | |
| 390 | &.{ | |
| 391 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, | |
| 392 | .ObjectName = @constCast(&sub_path_w.string()), | |
| 393 | }, | |
| 394 | &iosb, | |
| 395 | null, | |
| 396 | .{}, | |
| 397 | .VALID_FLAGS, | |
| 398 | .OPEN, | |
| 399 | .{ | |
| 400 | .DIRECTORY_FILE = true, | |
| 401 | .IO = .ASYNCHRONOUS, | |
| 402 | .OPEN_FOR_BACKUP_INTENT = true, | |
| 403 | }, | |
| 404 | null, | |
| 405 | 0, | |
| 406 | )) { | |
| 407 | .SUCCESS => {}, | |
| 408 | .OBJECT_NAME_INVALID => return error.BadPathName, | |
| 409 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 410 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 411 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 412 | .NOT_A_DIRECTORY => return error.NotDir, | |
| 413 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' | |
| 414 | .ACCESS_DENIED => return error.AccessDenied, | |
| 415 | .INVALID_PARAMETER => unreachable, | |
| 416 | else => |rc| return windows.unexpectedStatus(rc), | |
| 417 | } | |
| 418 | assert(dir_handle != windows.INVALID_HANDLE_VALUE); | |
| 419 | errdefer windows.CloseHandle(dir_handle); | |
| 420 | ||
| 421 | const dir_id = try getFileId(dir_handle); | |
| 422 | ||
| 423 | const dir = try gpa.create(Directory); | |
| 424 | dir.* = .{ | |
| 425 | .reaction_set = .empty, | |
| 426 | .id = dir_id, | |
| 427 | .file = .{ .handle = dir_handle, .flags = .{ .nonblocking = true } }, | |
| 428 | .state = .idle, | |
| 429 | .iosb = undefined, | |
| 430 | .buffer = undefined, | |
| 431 | .ready_node = undefined, | |
| 432 | }; | |
| 433 | return dir; | |
| 434 | } | |
| 435 | ||
| 436 | fn deinit(dir: *Directory, gpa: Allocator, w: *Watch) void { | |
| 437 | state: switch (dir.state) { | |
| 438 | .idle => {}, | |
| 439 | .listening => { | |
| 440 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; | |
| 441 | _ = windows.ntdll.NtCancelIoFileEx(dir.file.handle, &dir.iosb, &cancel_iosb); | |
| 442 | while (switch (dir.state) { | |
| 443 | .idle => unreachable, | |
| 444 | .listening => true, | |
| 445 | .ready => false, | |
| 446 | }) Io.Threaded.waitForApcOrAlert(); | |
| 447 | continue :state .ready; | |
| 448 | }, | |
| 449 | .ready => w.os.ready_dirs.remove(&dir.ready_node), | |
| 450 | } | |
| 451 | windows.CloseHandle(dir.file.handle); | |
| 452 | gpa.destroy(dir); | |
| 453 | } | |
| 454 | ||
| 455 | /// Useful to make `*Directory` a key in `std.ArrayHashMap`. | |
| 456 | const TableAdapter = struct { | |
| 457 | pub fn hash(_: TableAdapter, lhs_dir: *Directory) u32 { | |
| 458 | return @truncate(Hash.hash(lhs_dir.id.volumeSerialNumber, @ptrCast(&lhs_dir.id.indexNumber))); | |
| 459 | } | |
| 460 | pub fn eql(_: TableAdapter, lhs_dir: *Directory, rhs_dir: *Directory, rhs_index: usize) bool { | |
| 461 | _ = rhs_index; | |
| 462 | return lhs_dir.id.volumeSerialNumber == rhs_dir.id.volumeSerialNumber and | |
| 463 | lhs_dir.id.indexNumber == rhs_dir.id.indexNumber; | |
| 464 | } | |
| 465 | }; | |
| 466 | }; | |
| 467 | ||
| 468 | fn init(maker: *Maker) !Watch { | |
| 469 | return .{ | |
| 470 | .dir_table = .{}, | |
| 471 | .dir_count = 0, | |
| 472 | .os = switch (builtin.os.tag) { | |
| 473 | .windows => .{ | |
| 474 | .handle_table = .empty, | |
| 475 | .ready_dirs = .{}, | |
| 476 | }, | |
| 477 | else => {}, | |
| 478 | }, | |
| 479 | .generation = 0, | |
| 480 | .maker = maker, | |
| 481 | }; | |
| 482 | } | |
| 483 | ||
| 484 | fn getFileId(handle: windows.HANDLE) !FileId { | |
| 485 | var file_id: FileId = undefined; | |
| 486 | var io_status: windows.IO_STATUS_BLOCK = undefined; | |
| 487 | var volume_info: windows.FILE.FS_VOLUME_INFORMATION = undefined; | |
| 488 | switch (windows.ntdll.NtQueryVolumeInformationFile( | |
| 489 | handle, | |
| 490 | &io_status, | |
| 491 | &volume_info, | |
| 492 | @sizeOf(windows.FILE.FS_VOLUME_INFORMATION), | |
| 493 | .Volume, | |
| 494 | )) { | |
| 495 | .SUCCESS => {}, | |
| 496 | // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer | |
| 497 | // size provided. This is treated as success because the type of variable-length information that this would be relevant for | |
| 498 | // (name, volume name, etc) we don't care about. | |
| 499 | .BUFFER_OVERFLOW => {}, | |
| 500 | else => |rc| return windows.unexpectedStatus(rc), | |
| 501 | } | |
| 502 | file_id.volumeSerialNumber = volume_info.VolumeSerialNumber; | |
| 503 | var internal_info: windows.FILE.INTERNAL_INFORMATION = undefined; | |
| 504 | switch (windows.ntdll.NtQueryInformationFile( | |
| 505 | handle, | |
| 506 | &io_status, | |
| 507 | &internal_info, | |
| 508 | @sizeOf(windows.FILE.INTERNAL_INFORMATION), | |
| 509 | .Internal, | |
| 510 | )) { | |
| 511 | .SUCCESS => {}, | |
| 512 | else => |rc| return windows.unexpectedStatus(rc), | |
| 513 | } | |
| 514 | file_id.indexNumber = internal_info.IndexNumber; | |
| 515 | return file_id; | |
| 516 | } | |
| 517 | ||
| 518 | fn markDirtySteps(w: *Watch, dir: *Directory) !bool { | |
| 519 | const maker = w.maker; | |
| 520 | ||
| 521 | var any_dirty = false; | |
| 522 | const bytes_returned = dir.iosb.Information; | |
| 523 | if (bytes_returned == 0) { | |
| 524 | std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); | |
| 525 | markAllFilesDirty(w); | |
| 526 | try dir.startListening(w); | |
| 527 | return true; | |
| 528 | } | |
| 529 | var file_name_buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 530 | var offset: usize = 0; | |
| 531 | while (true) { | |
| 532 | const notify: *windows.FILE.NOTIFY.INFORMATION = @ptrCast(@alignCast(&dir.buffer[offset])); | |
| 533 | const file_name = file_name_buf[0..std.unicode.wtf16LeToWtf8(&file_name_buf, notify.fileName())]; | |
| 534 | if (dir.reaction_set.getPtr(".")) |glob_set| | |
| 535 | any_dirty = markStepSetDirty(maker, glob_set, any_dirty); | |
| 536 | if (dir.reaction_set.getPtr(file_name)) |step_set| | |
| 537 | any_dirty = markStepSetDirty(maker, step_set, any_dirty); | |
| 538 | if (notify.NextEntryOffset == 0) | |
| 539 | break; | |
| 540 | ||
| 541 | offset += notify.NextEntryOffset; | |
| 542 | } | |
| 543 | ||
| 544 | // We call this now since at this point we have finished reading dir.buffer. | |
| 545 | try dir.startListening(w); | |
| 546 | return any_dirty; | |
| 547 | } | |
| 548 | ||
| 549 | fn update(w: *Watch, steps: []const Configuration.Step.Index) !void { | |
| 550 | const maker = w.maker; | |
| 551 | const gpa = maker.gpa; | |
| 552 | // Add missing marks and note persisted ones. | |
| 553 | for (steps) |step_index| { | |
| 554 | const step = maker.stepByIndex(step_index); | |
| 555 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | |
| 556 | const dir = dir: { | |
| 557 | const gop = try w.dir_table.getOrPut(gpa, path); | |
| 558 | if (!gop.found_existing) { | |
| 559 | const dir: *Directory = try .init(gpa, path); | |
| 560 | errdefer dir.deinit(gpa, w); | |
| 561 | // `dir.id` may already be present in the table in | |
| 562 | // the case that we have multiple Cache.Path instances | |
| 563 | // that compare inequal but ultimately point to the same | |
| 564 | // directory on the file system. | |
| 565 | // In such case, we must revert adding this directory, but keep | |
| 566 | // the additions to the step set. | |
| 567 | const dh_gop = try w.os.handle_table.getOrPut(gpa, dir); | |
| 568 | if (dh_gop.found_existing) { | |
| 569 | dir.deinit(gpa, w); | |
| 570 | _ = w.dir_table.pop(); | |
| 571 | break :dir w.os.handle_table.keys()[dh_gop.index]; | |
| 572 | } else { | |
| 573 | assert(dh_gop.index == gop.index); | |
| 574 | try dir.startListening(w); | |
| 575 | break :dir dir; | |
| 576 | } | |
| 577 | } | |
| 578 | break :dir w.os.handle_table.keys()[gop.index]; | |
| 579 | }; | |
| 580 | for (files.items) |basename| { | |
| 581 | const gop = try dir.reaction_set.getOrPut(gpa, basename); | |
| 582 | if (!gop.found_existing) gop.value_ptr.* = .{}; | |
| 583 | try gop.value_ptr.put(gpa, step_index, w.generation); | |
| 584 | } | |
| 585 | } | |
| 586 | } | |
| 587 | ||
| 588 | { | |
| 589 | // Remove marks for files that are no longer inputs. | |
| 590 | var i: usize = 0; | |
| 591 | while (i < w.os.handle_table.entries.len) { | |
| 592 | const dir = w.os.handle_table.keys()[i]; | |
| 593 | { | |
| 594 | var step_set_i: usize = 0; | |
| 595 | while (step_set_i < dir.reaction_set.entries.len) { | |
| 596 | const step_set = &dir.reaction_set.values()[step_set_i]; | |
| 597 | var dirent_i: usize = 0; | |
| 598 | while (dirent_i < step_set.entries.len) { | |
| 599 | const generations = step_set.values(); | |
| 600 | if (generations[dirent_i] == w.generation) { | |
| 601 | dirent_i += 1; | |
| 602 | continue; | |
| 603 | } | |
| 604 | step_set.swapRemoveAt(dirent_i); | |
| 605 | } | |
| 606 | if (step_set.entries.len > 0) { | |
| 607 | step_set_i += 1; | |
| 608 | continue; | |
| 609 | } | |
| 610 | dir.reaction_set.swapRemoveAt(step_set_i); | |
| 611 | } | |
| 612 | if (dir.reaction_set.entries.len > 0) { | |
| 613 | i += 1; | |
| 614 | continue; | |
| 615 | } | |
| 616 | } | |
| 617 | ||
| 618 | w.dir_table.swapRemoveAt(i); | |
| 619 | w.os.handle_table.swapRemoveAt(i); | |
| 620 | dir.deinit(gpa, w); | |
| 621 | } | |
| 622 | w.generation +%= 1; | |
| 623 | } | |
| 624 | w.dir_count = w.dir_table.count(); | |
| 625 | } | |
| 626 | ||
| 627 | fn wait(w: *Watch, timeout: Timeout) !WaitResult { | |
| 628 | const maker = w.maker; | |
| 629 | const io = maker.graph.io; | |
| 630 | ||
| 631 | for (0..2) |attempt| { | |
| 632 | while (w.os.ready_dirs.popFirst()) |ready_node| { | |
| 633 | const dir: *Directory = @fieldParentPtr("ready_node", ready_node); | |
| 634 | assert(dir.state == .ready); | |
| 635 | dir.state = .idle; | |
| 636 | switch (dir.iosb.u.Status) { | |
| 637 | .SUCCESS => return if (try markDirtySteps(w, dir)) .dirty else .clean, | |
| 638 | .PENDING => unreachable, | |
| 639 | .CANCELLED => {}, | |
| 640 | else => |status| return windows.unexpectedStatus(status), | |
| 641 | } | |
| 642 | try dir.startListening(w); | |
| 643 | } | |
| 644 | try io.checkCancel(); | |
| 645 | if (attempt == 1) return .timeout; | |
| 646 | const delay_interval: windows.LARGE_INTEGER = switch (timeout) { | |
| 647 | .none => std.math.minInt(windows.LARGE_INTEGER), | |
| 648 | .ms => |ms| -@as(windows.LARGE_INTEGER, ms) * (std.time.ns_per_ms / 100), | |
| 649 | }; | |
| 650 | _ = windows.ntdll.NtDelayExecution(.TRUE, &delay_interval); | |
| 651 | } else unreachable; | |
| 652 | } | |
| 653 | }, | |
| 654 | .dragonfly, .freebsd, .netbsd, .openbsd, .ios, .tvos, .visionos, .watchos => struct { | |
| 655 | const posix = std.posix; | |
| 656 | ||
| 657 | kq_fd: i32, | |
| 658 | /// Indexes correspond 1:1 with `dir_table`. | |
| 659 | handles: std.MultiArrayList(struct { | |
| 660 | rs: ReactionSet, | |
| 661 | /// If the corresponding dir_table Path has sub_path == "", then it | |
| 662 | /// suffices as the open directory handle, and this value will be | |
| 663 | /// -1. Otherwise, it needs to be opened in update(), and will be | |
| 664 | /// stored here. | |
| 665 | dir_fd: i32, | |
| 666 | }), | |
| 667 | ||
| 668 | const dir_open_flags: posix.O = f: { | |
| 669 | var f: posix.O = .{ | |
| 670 | .ACCMODE = .RDONLY, | |
| 671 | .NOFOLLOW = false, | |
| 672 | .DIRECTORY = true, | |
| 673 | .CLOEXEC = true, | |
| 674 | }; | |
| 675 | if (@hasField(posix.O, "EVTONLY")) f.EVTONLY = true; | |
| 676 | if (@hasField(posix.O, "PATH")) f.PATH = true; | |
| 677 | break :f f; | |
| 678 | }; | |
| 679 | ||
| 680 | const EV = std.c.EV; | |
| 681 | const NOTE = std.c.NOTE; | |
| 682 | ||
| 683 | fn init(maker: *Maker) !Watch { | |
| 684 | return .{ | |
| 685 | .dir_table = .{}, | |
| 686 | .dir_count = 0, | |
| 687 | .os = .{ | |
| 688 | .kq_fd = try Io.Kqueue.createFileDescriptor(), | |
| 689 | .handles = .empty, | |
| 690 | }, | |
| 691 | .generation = 0, | |
| 692 | .maker = maker, | |
| 693 | }; | |
| 694 | } | |
| 695 | ||
| 696 | fn update(w: *Watch, steps: []const Configuration.Step.Index) !void { | |
| 697 | const maker = w.maker; | |
| 698 | const gpa = maker.gpa; | |
| 699 | const handles = &w.os.handles; | |
| 700 | for (steps) |step_index| { | |
| 701 | const step = maker.stepByIndex(step_index); | |
| 702 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | |
| 703 | const reaction_set = rs: { | |
| 704 | const gop = try w.dir_table.getOrPut(gpa, path); | |
| 705 | if (!gop.found_existing) { | |
| 706 | const skip_open_dir = path.sub_path.len == 0; | |
| 707 | const dir_fd = if (skip_open_dir) | |
| 708 | path.root_dir.handle.handle | |
| 709 | else | |
| 710 | posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| { | |
| 711 | fatal("failed to open directory {f}: {t}", .{ path, err }); | |
| 712 | }; | |
| 713 | // Empirically the dir has to stay open or else no events are triggered. | |
| 714 | errdefer if (!skip_open_dir) std.Io.Threaded.closeFd(dir_fd); | |
| 715 | const changes = [1]posix.Kevent{.{ | |
| 716 | .ident = @bitCast(@as(isize, dir_fd)), | |
| 717 | .filter = std.c.EVFILT.VNODE, | |
| 718 | .flags = EV.ADD | EV.ENABLE | EV.CLEAR, | |
| 719 | .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE, | |
| 720 | .data = 0, | |
| 721 | .udata = gop.index, | |
| 722 | }}; | |
| 723 | _ = try Io.Kqueue.kevent(w.os.kq_fd, &changes, &.{}, null); | |
| 724 | assert(handles.len == gop.index); | |
| 725 | try handles.append(gpa, .{ | |
| 726 | .rs = .{}, | |
| 727 | .dir_fd = if (skip_open_dir) -1 else dir_fd, | |
| 728 | }); | |
| 729 | } | |
| 730 | ||
| 731 | break :rs &handles.items(.rs)[gop.index]; | |
| 732 | }; | |
| 733 | for (files.items) |basename| { | |
| 734 | const gop = try reaction_set.getOrPut(gpa, basename); | |
| 735 | if (!gop.found_existing) gop.value_ptr.* = .{}; | |
| 736 | try gop.value_ptr.put(gpa, step_index, w.generation); | |
| 737 | } | |
| 738 | } | |
| 739 | } | |
| 740 | ||
| 741 | { | |
| 742 | // Remove marks for files that are no longer inputs. | |
| 743 | var i: usize = 0; | |
| 744 | while (i < handles.len) { | |
| 745 | { | |
| 746 | const reaction_set = &handles.items(.rs)[i]; | |
| 747 | var step_set_i: usize = 0; | |
| 748 | while (step_set_i < reaction_set.entries.len) { | |
| 749 | const step_set = &reaction_set.values()[step_set_i]; | |
| 750 | var dirent_i: usize = 0; | |
| 751 | while (dirent_i < step_set.entries.len) { | |
| 752 | const generations = step_set.values(); | |
| 753 | if (generations[dirent_i] == w.generation) { | |
| 754 | dirent_i += 1; | |
| 755 | continue; | |
| 756 | } | |
| 757 | step_set.swapRemoveAt(dirent_i); | |
| 758 | } | |
| 759 | if (step_set.entries.len > 0) { | |
| 760 | step_set_i += 1; | |
| 761 | continue; | |
| 762 | } | |
| 763 | reaction_set.swapRemoveAt(step_set_i); | |
| 764 | } | |
| 765 | if (reaction_set.entries.len > 0) { | |
| 766 | i += 1; | |
| 767 | continue; | |
| 768 | } | |
| 769 | } | |
| 770 | ||
| 771 | // If the sub_path == "" then this patch has already the | |
| 772 | // dir fd that we need to use as the ident to remove the | |
| 773 | // event. If it was opened above with openat() then we need | |
| 774 | // to access that data via the dir_fd field. | |
| 775 | const path = w.dir_table.keys()[i]; | |
| 776 | const dir_fd = if (path.sub_path.len == 0) | |
| 777 | path.root_dir.handle.handle | |
| 778 | else | |
| 779 | handles.items(.dir_fd)[i]; | |
| 780 | assert(dir_fd != -1); | |
| 781 | ||
| 782 | // The changelist also needs to update the udata field of the last | |
| 783 | // event, since we are doing a swap remove, and we store the dir_table | |
| 784 | // index in the udata field. | |
| 785 | const last_dir_fd = fd: { | |
| 786 | const last_path = w.dir_table.keys()[handles.len - 1]; | |
| 787 | const last_dir_fd = if (last_path.sub_path.len == 0) | |
| 788 | last_path.root_dir.handle.handle | |
| 789 | else | |
| 790 | handles.items(.dir_fd)[handles.len - 1]; | |
| 791 | assert(last_dir_fd != -1); | |
| 792 | break :fd last_dir_fd; | |
| 793 | }; | |
| 794 | const changes = [_]posix.Kevent{ | |
| 795 | .{ | |
| 796 | .ident = @bitCast(@as(isize, dir_fd)), | |
| 797 | .filter = std.c.EVFILT.VNODE, | |
| 798 | .flags = EV.DELETE, | |
| 799 | .fflags = 0, | |
| 800 | .data = 0, | |
| 801 | .udata = i, | |
| 802 | }, | |
| 803 | .{ | |
| 804 | .ident = @bitCast(@as(isize, last_dir_fd)), | |
| 805 | .filter = std.c.EVFILT.VNODE, | |
| 806 | .flags = EV.ADD, | |
| 807 | .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE, | |
| 808 | .data = 0, | |
| 809 | .udata = i, | |
| 810 | }, | |
| 811 | }; | |
| 812 | const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes; | |
| 813 | _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null); | |
| 814 | if (path.sub_path.len != 0) std.Io.Threaded.closeFd(dir_fd); | |
| 815 | ||
| 816 | w.dir_table.swapRemoveAt(i); | |
| 817 | handles.swapRemove(i); | |
| 818 | } | |
| 819 | w.generation +%= 1; | |
| 820 | } | |
| 821 | w.dir_count = w.dir_table.count(); | |
| 822 | } | |
| 823 | ||
| 824 | fn wait(w: *Watch, timeout: Timeout) !WaitResult { | |
| 825 | const maker = w.maker; | |
| 826 | var timespec_buffer: posix.timespec = undefined; | |
| 827 | var event_buffer: [100]posix.Kevent = undefined; | |
| 828 | var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer)); | |
| 829 | if (n == 0) return .timeout; | |
| 830 | const reaction_sets = w.os.handles.items(.rs); | |
| 831 | var any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], false); | |
| 832 | timespec_buffer = .{ .sec = 0, .nsec = 0 }; | |
| 833 | while (n == event_buffer.len) { | |
| 834 | n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer); | |
| 835 | if (n == 0) break; | |
| 836 | any_dirty = markDirtySteps(maker, reaction_sets, event_buffer[0..n], any_dirty); | |
| 837 | } | |
| 838 | return if (any_dirty) .dirty else .clean; | |
| 839 | } | |
| 840 | ||
| 841 | fn markDirtySteps( | |
| 842 | maker: *Maker, | |
| 843 | reaction_sets: []ReactionSet, | |
| 844 | events: []const std.c.Kevent, | |
| 845 | start_any_dirty: bool, | |
| 846 | ) bool { | |
| 847 | var any_dirty = start_any_dirty; | |
| 848 | for (events) |event| { | |
| 849 | const index: usize = @intCast(event.udata); | |
| 850 | const reaction_set = &reaction_sets[index]; | |
| 851 | // If we knew the basename of the changed file, here we would | |
| 852 | // mark only the step set dirty, and possibly the glob set: | |
| 853 | //if (reaction_set.getPtr(".")) |glob_set| | |
| 854 | // any_dirty = markStepSetDirty(maker, glob_set, any_dirty); | |
| 855 | //if (reaction_set.getPtr(file_name)) |step_set| | |
| 856 | // any_dirty = markStepSetDirty(maker, step_set, any_dirty); | |
| 857 | // However we don't know the file name so just mark all the | |
| 858 | // sets dirty for this directory. | |
| 859 | for (reaction_set.values()) |*step_set| { | |
| 860 | any_dirty = markStepSetDirty(maker, step_set, any_dirty); | |
| 861 | } | |
| 862 | } | |
| 863 | return any_dirty; | |
| 864 | } | |
| 865 | }, | |
| 866 | .macos => struct { | |
| 867 | fse: FsEvents, | |
| 868 | ||
| 869 | fn init(maker: *Maker) !Watch { | |
| 870 | return .{ | |
| 871 | .os = .{ .fse = try .init(maker.graph.cache.cwd) }, | |
| 872 | .dir_count = 0, | |
| 873 | .dir_table = undefined, | |
| 874 | .generation = undefined, | |
| 875 | .maker = maker, | |
| 876 | }; | |
| 877 | } | |
| 878 | fn update(w: *Watch, steps: []const Configuration.Step.Index) !void { | |
| 879 | try w.os.fse.setPaths(w.maker, steps); | |
| 880 | w.dir_count = w.os.fse.watch_roots.len; | |
| 881 | } | |
| 882 | fn wait(w: *Watch, timeout: Timeout) !WaitResult { | |
| 883 | return w.os.fse.wait(w.maker, switch (timeout) { | |
| 884 | .none => null, | |
| 885 | .ms => |ms| @as(u64, ms) * std.time.ns_per_ms, | |
| 886 | }); | |
| 887 | } | |
| 888 | }, | |
| 889 | else => void, | |
| 890 | }; | |
| 891 | ||
| 892 | pub fn init(maker: *Maker) !Watch { | |
| 893 | return Os.init(maker); | |
| 894 | } | |
| 895 | ||
| 896 | pub const Match = struct { | |
| 897 | /// Relative to the watched directory, the file path that triggers this | |
| 898 | /// match. | |
| 899 | basename: []const u8, | |
| 900 | /// The step to re-run when file corresponding to `basename` is changed. | |
| 901 | step_index: Configuration.Step.Index, | |
| 902 | ||
| 903 | pub const Context = struct { | |
| 904 | pub fn hash(self: Context, a: Match) u32 { | |
| 905 | _ = self; | |
| 906 | var hasher = Hash.init(@intFromEnum(a.step_index)); | |
| 907 | hasher.update(a.basename); | |
| 908 | return @truncate(hasher.final()); | |
| 909 | } | |
| 910 | pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool { | |
| 911 | _ = self; | |
| 912 | _ = b_index; | |
| 913 | return a.step_index == b.step_index and std.mem.eql(u8, a.basename, b.basename); | |
| 914 | } | |
| 915 | }; | |
| 916 | }; | |
| 917 | ||
| 918 | fn markAllFilesDirty(w: *Watch) void { | |
| 919 | const maker = w.maker; | |
| 920 | ||
| 921 | for (switch (builtin.os.tag) { | |
| 922 | .windows => w.os.handle_table.keys(), | |
| 923 | else => w.os.handle_table.values(), | |
| 924 | }) |item| { | |
| 925 | const reaction_set = switch (builtin.os.tag) { | |
| 926 | .linux, .windows => item.reaction_set, | |
| 927 | else => item, | |
| 928 | }; | |
| 929 | for (reaction_set.values()) |step_set| { | |
| 930 | for (step_set.keys()) |step_index| { | |
| 931 | const step = maker.stepByIndex(step_index); | |
| 932 | _ = maker.invalidateResult(step); | |
| 933 | } | |
| 934 | } | |
| 935 | } | |
| 936 | } | |
| 937 | ||
| 938 | fn markStepSetDirty(maker: *Maker, step_set: *StepSet, any_dirty: bool) bool { | |
| 939 | var this_any_dirty = false; | |
| 940 | for (step_set.keys()) |step_index| { | |
| 941 | const step = maker.stepByIndex(step_index); | |
| 942 | if (maker.invalidateResult(step)) this_any_dirty = true; | |
| 943 | } | |
| 944 | return any_dirty or this_any_dirty; | |
| 945 | } | |
| 946 | ||
| 947 | pub fn update(w: *Watch, steps: []const Configuration.Step.Index) !void { | |
| 948 | return Os.update(w, steps); | |
| 949 | } | |
| 950 | ||
| 951 | pub const Timeout = union(enum) { | |
| 952 | none, | |
| 953 | ms: u16, | |
| 954 | ||
| 955 | pub fn to_i32_ms(t: Timeout) i32 { | |
| 956 | return switch (t) { | |
| 957 | .none => -1, | |
| 958 | .ms => |ms| ms, | |
| 959 | }; | |
| 960 | } | |
| 961 | ||
| 962 | pub fn toTimespec(t: Timeout, buf: *std.posix.timespec) ?*std.posix.timespec { | |
| 963 | return switch (t) { | |
| 964 | .none => null, | |
| 965 | .ms => |ms_u16| { | |
| 966 | const ms: isize = ms_u16; | |
| 967 | buf.* = .{ | |
| 968 | .sec = @divTrunc(ms, std.time.ms_per_s), | |
| 969 | .nsec = @rem(ms, std.time.ms_per_s) * std.time.ns_per_ms, | |
| 970 | }; | |
| 971 | return buf; | |
| 972 | }, | |
| 973 | }; | |
| 974 | } | |
| 975 | }; | |
| 976 | ||
| 977 | pub const WaitResult = enum { | |
| 978 | timeout, | |
| 979 | /// File system watching triggered on files that were marked as inputs to at least one Step. | |
| 980 | /// Relevant steps have been marked dirty. | |
| 981 | dirty, | |
| 982 | /// File system watching triggered but none of the events were relevant to | |
| 983 | /// what we are listening to. There is nothing to do. | |
| 984 | clean, | |
| 985 | }; | |
| 986 | ||
| 987 | pub fn wait(w: *Watch, timeout: Timeout) !WaitResult { | |
| 988 | return Os.wait(w, timeout); | |
| 989 | } |
lib/compiler/Maker/Watch/FsEvents.zig created+488| ... | ... | @@ -0,0 +1,488 @@ |
| 1 | //! An implementation of file-system watching based on the `FSEventStream` API in macOS. | |
| 2 | //! While macOS supports kqueue, it does not allow detecting changes to files without | |
| 3 | //! placing watches on each individual file, meaning FD limits are reached incredibly | |
| 4 | //! quickly. The File System Events API works differently: it implements *recursive* | |
| 5 | //! directory watches, managed by a system service. Rather than being in libc, the API is | |
| 6 | //! exposed by the CoreServices framework. To avoid a compile dependency on the framework | |
| 7 | //! bundle, we dynamically load CoreServices with `std.DynLib`. | |
| 8 | //! | |
| 9 | //! While the logic in this file *is* specialized to `std.Build.Watch`, efforts have been | |
| 10 | //! made to keep that specialization to a minimum. Other use cases could be served with | |
| 11 | //! relatively minimal modifications to the `watch_paths` field and its usages (in | |
| 12 | //! particular the `setPaths` function). We avoid using the global GCD dispatch queue in | |
| 13 | //! favour of creating our own and synchronizing with an explicit semaphore, meaning this | |
| 14 | //! logic is thread-safe and does not affect process-global state. | |
| 15 | //! | |
| 16 | //! In theory, this API is quite good at avoiding filesystem race conditions. In practice, | |
| 17 | //! the logic that would avoid them is currently disabled, because the build system kind | |
| 18 | //! of relies on them at the time of writing to avoid redundant work -- see the comment at | |
| 19 | //! the top of `wait` for details. | |
| 20 | const FsEvents = @This(); | |
| 21 | ||
| 22 | const enable_debug_logs = false; | |
| 23 | ||
| 24 | core_services: std.DynLib, | |
| 25 | resolved_symbols: ResolvedSymbols, | |
| 26 | ||
| 27 | paths_arena: std.heap.ArenaAllocator.State, | |
| 28 | /// The roots of the recursive watches. FSEvents has relatively small limits on the number | |
| 29 | /// of watched paths, so this slice must not be too long. The paths themselves are allocated | |
| 30 | /// into `paths_arena`, but this slice is allocated into the GPA. | |
| 31 | watch_roots: [][:0]const u8, | |
| 32 | /// All of the paths being watched. Value is the set of steps which depend on the file/directory. | |
| 33 | /// Keys and values are in `paths_arena`, but this map is allocated into the GPA. | |
| 34 | watch_paths: std.array_hash_map.String([]const std.Build.Configuration.Step.Index), | |
| 35 | ||
| 36 | /// The semaphore we use to block the thread calling `wait` until the callback determines a relevant | |
| 37 | /// event has occurred. This is retained across `wait` calls for simplicity and efficiency. | |
| 38 | waiting_semaphore: dispatch.semaphore_t, | |
| 39 | /// This dispatch queue is created by us and executes serially. It exists exclusively to trigger the | |
| 40 | /// callbacks of the FSEventStream we create. This is not in use outside of `wait`, but is retained | |
| 41 | /// across `wait` calls for simplicity and efficiency. | |
| 42 | dispatch_queue: dispatch.queue_t, | |
| 43 | /// In theory, this field avoids race conditions. In practice, it is essentially unused at the time | |
| 44 | /// of writing. See the comment at the start of `wait` for details. | |
| 45 | since_event: FSEventStreamEventId, | |
| 46 | ||
| 47 | cwd_path: []const u8, | |
| 48 | ||
| 49 | /// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols | |
| 50 | /// is not present, `init` will close the framework and return an error. | |
| 51 | const ResolvedSymbols = struct { | |
| 52 | FSEventStreamCreate: *const fn ( | |
| 53 | allocator: CFAllocatorRef, | |
| 54 | callback: FSEventStreamCallback, | |
| 55 | ctx: ?*const FSEventStreamContext, | |
| 56 | paths_to_watch: CFArrayRef, | |
| 57 | since_when: FSEventStreamEventId, | |
| 58 | latency: CFTimeInterval, | |
| 59 | flags: FSEventStreamCreateFlags, | |
| 60 | ) callconv(.c) FSEventStreamRef, | |
| 61 | FSEventStreamSetDispatchQueue: *const fn (stream: FSEventStreamRef, queue: dispatch.queue_t) callconv(.c) void, | |
| 62 | FSEventStreamStart: *const fn (stream: FSEventStreamRef) callconv(.c) bool, | |
| 63 | FSEventStreamStop: *const fn (stream: FSEventStreamRef) callconv(.c) void, | |
| 64 | FSEventStreamInvalidate: *const fn (stream: FSEventStreamRef) callconv(.c) void, | |
| 65 | FSEventStreamRelease: *const fn (stream: FSEventStreamRef) callconv(.c) void, | |
| 66 | FSEventStreamGetLatestEventId: *const fn (stream: ConstFSEventStreamRef) callconv(.c) FSEventStreamEventId, | |
| 67 | FSEventsGetCurrentEventId: *const fn () callconv(.c) FSEventStreamEventId, | |
| 68 | CFRelease: *const fn (cf: *const anyopaque) callconv(.c) void, | |
| 69 | CFArrayCreate: *const fn ( | |
| 70 | allocator: CFAllocatorRef, | |
| 71 | values: [*]const usize, | |
| 72 | num_values: CFIndex, | |
| 73 | call_backs: ?*const CFArrayCallBacks, | |
| 74 | ) callconv(.c) CFArrayRef, | |
| 75 | CFStringCreateWithCString: *const fn ( | |
| 76 | alloc: CFAllocatorRef, | |
| 77 | c_str: [*:0]const u8, | |
| 78 | encoding: CFStringEncoding, | |
| 79 | ) callconv(.c) CFStringRef, | |
| 80 | CFAllocatorCreate: *const fn (allocator: CFAllocatorRef, context: *const CFAllocatorContext) callconv(.c) CFAllocatorRef, | |
| 81 | kCFAllocatorUseContext: *const CFAllocatorRef, | |
| 82 | }; | |
| 83 | ||
| 84 | pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol, SystemResources }!FsEvents { | |
| 85 | var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch | |
| 86 | return error.OpenFrameworkFailed; | |
| 87 | errdefer core_services.close(); | |
| 88 | ||
| 89 | var resolved_symbols: ResolvedSymbols = undefined; | |
| 90 | inline for (@typeInfo(ResolvedSymbols).@"struct".fields) |f| { | |
| 91 | @field(resolved_symbols, f.name) = core_services.lookup(f.type, f.name) orelse return error.MissingCoreServicesSymbol; | |
| 92 | } | |
| 93 | ||
| 94 | return .{ | |
| 95 | .core_services = core_services, | |
| 96 | .resolved_symbols = resolved_symbols, | |
| 97 | .paths_arena = .{}, | |
| 98 | .watch_roots = &.{}, | |
| 99 | .watch_paths = .empty, | |
| 100 | .waiting_semaphore = dispatch.semaphore_create(0) orelse return error.SystemResources, | |
| 101 | .dispatch_queue = dispatch.queue_create("zig-watch", .SERIAL()) orelse return error.SystemResources, | |
| 102 | // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order | |
| 103 | // to notice any changes which happened during said work. | |
| 104 | .since_event = resolved_symbols.FSEventsGetCurrentEventId(), | |
| 105 | .cwd_path = cwd_path, | |
| 106 | }; | |
| 107 | } | |
| 108 | ||
| 109 | pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void { | |
| 110 | fse.waiting_semaphore.as_object().release(); | |
| 111 | fse.dispatch_queue.as_object().release(); | |
| 112 | fse.core_services.close(io); | |
| 113 | ||
| 114 | gpa.free(fse.watch_roots); | |
| 115 | fse.watch_paths.deinit(gpa); | |
| 116 | { | |
| 117 | var paths_arena = fse.paths_arena.promote(gpa); | |
| 118 | paths_arena.deinit(); | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | pub fn setPaths(fse: *FsEvents, maker: *Maker, steps: []const std.Build.Configuration.Step.Index) !void { | |
| 123 | const gpa = maker.gpa; | |
| 124 | ||
| 125 | var paths_arena_instance = fse.paths_arena.promote(gpa); | |
| 126 | defer fse.paths_arena = paths_arena_instance.state; | |
| 127 | const paths_arena = paths_arena_instance.allocator(); | |
| 128 | ||
| 129 | var need_dirs: std.array_hash_map.String(void) = .empty; | |
| 130 | defer need_dirs.deinit(gpa); | |
| 131 | ||
| 132 | fse.watch_paths.clearRetainingCapacity(); | |
| 133 | ||
| 134 | // We take `step_index` by pointer for a slight memory optimization in a moment. | |
| 135 | for (steps) |*step_index| { | |
| 136 | const step = maker.stepByIndex(step_index.*); | |
| 137 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | |
| 138 | const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ | |
| 139 | fse.cwd_path, path.root_dir.path orelse ".", path.sub_path, | |
| 140 | }); | |
| 141 | try need_dirs.put(gpa, resolved_dir, {}); | |
| 142 | for (files.items) |file_name| { | |
| 143 | const watch_path = if (std.mem.eql(u8, file_name, ".")) | |
| 144 | resolved_dir | |
| 145 | else | |
| 146 | try std.fs.path.join(paths_arena, &.{ resolved_dir, file_name }); | |
| 147 | const gop = try fse.watch_paths.getOrPut(gpa, watch_path); | |
| 148 | if (gop.found_existing) { | |
| 149 | const old_steps = gop.value_ptr.*; | |
| 150 | const new_steps = try paths_arena.alloc(std.Build.Configuration.Step.Index, old_steps.len + 1); | |
| 151 | @memcpy(new_steps[0..old_steps.len], old_steps); | |
| 152 | new_steps[old_steps.len] = step_index.*; | |
| 153 | gop.value_ptr.* = new_steps; | |
| 154 | } else { | |
| 155 | // This is why we captured `step` by pointer! We can avoid allocating a slice of one | |
| 156 | // step in the arena in the common case where a file is referenced by only one step. | |
| 157 | gop.value_ptr.* = step_index[0..1]; | |
| 158 | } | |
| 159 | } | |
| 160 | } | |
| 161 | } | |
| 162 | ||
| 163 | { | |
| 164 | // There's no point looking at directories inside other ones (e.g. "/foo" and "/foo/bar"). | |
| 165 | // To eliminate these, we'll re-add directories in order of path length with a redundancy check. | |
| 166 | const old_dirs = try gpa.dupe([]const u8, need_dirs.keys()); | |
| 167 | defer gpa.free(old_dirs); | |
| 168 | std.mem.sort([]const u8, old_dirs, {}, struct { | |
| 169 | fn lessThan(ctx: void, a: []const u8, b: []const u8) bool { | |
| 170 | ctx; | |
| 171 | return std.mem.lessThan(u8, a, b); | |
| 172 | } | |
| 173 | }.lessThan); | |
| 174 | need_dirs.clearRetainingCapacity(); | |
| 175 | for (old_dirs) |dir_path| { | |
| 176 | var it: std.fs.path.ComponentIterator(.posix, u8) = .init(dir_path); | |
| 177 | while (it.next()) |component| { | |
| 178 | if (need_dirs.contains(component.path)) { | |
| 179 | // this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added | |
| 180 | break; | |
| 181 | } | |
| 182 | } else { | |
| 183 | need_dirs.putAssumeCapacityNoClobber(dir_path, {}); | |
| 184 | } | |
| 185 | } | |
| 186 | } | |
| 187 | ||
| 188 | // `need_dirs` is now a set of directories to watch with no redundancy. In practice, this is very | |
| 189 | // likely to have reduced it to a quite small set (e.g. it'll typically coalesce a full `src/` | |
| 190 | // directory into one entry). However, the FSEventStream API has a fairly low undocumented limit | |
| 191 | // on total watches (supposedly 4096), so we should handle the case where we exceed it. To be | |
| 192 | // safe, because this API can be a little unpredictable, we'll cap ourselves a little *below* | |
| 193 | // that known limit. | |
| 194 | if (need_dirs.count() > 2048) { | |
| 195 | // Fallback: watch the whole filesystem. This is excessive, but... it *works* :P | |
| 196 | if (enable_debug_logs) watch_log.debug("too many dirs; recursively watching root", .{}); | |
| 197 | fse.watch_roots = try gpa.realloc(fse.watch_roots, 1); | |
| 198 | fse.watch_roots[0] = "/"; | |
| 199 | } else { | |
| 200 | fse.watch_roots = try gpa.realloc(fse.watch_roots, need_dirs.count()); | |
| 201 | for (fse.watch_roots, need_dirs.keys()) |*out, in| { | |
| 202 | out.* = try paths_arena.dupeSentinel(u8, in, 0); | |
| 203 | } | |
| 204 | } | |
| 205 | if (enable_debug_logs) { | |
| 206 | watch_log.debug("watching {d} paths using {d} recursive watches:", .{ fse.watch_paths.count(), fse.watch_roots.len }); | |
| 207 | for (fse.watch_roots) |dir_path| { | |
| 208 | watch_log.debug("- '{s}'", .{dir_path}); | |
| 209 | } | |
| 210 | } | |
| 211 | } | |
| 212 | ||
| 213 | pub fn wait(fse: *FsEvents, maker: *Maker, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!Watch.WaitResult { | |
| 214 | if (fse.watch_roots.len == 0) @panic("nothing to watch"); | |
| 215 | const gpa = maker.gpa; | |
| 216 | ||
| 217 | const rs = fse.resolved_symbols; | |
| 218 | ||
| 219 | // At the time of writing, using `since_event` in the obvious way causes redundant rebuilds | |
| 220 | // to occur, because one step modifies a file which is an input to another step. The solution | |
| 221 | // to this problem will probably be either: | |
| 222 | // | |
| 223 | // a) Don't include the output of one step as a watch input of another; only mark external | |
| 224 | // files as watch inputs. Or... | |
| 225 | // | |
| 226 | // b) Note the current event ID when a step begins, and disregard events preceding that ID | |
| 227 | // when considering whether to dirty that step in `eventCallback`. | |
| 228 | // | |
| 229 | // For now, to avoid the redundant rebuilds, we bypass this `since_event` mechanism. This does | |
| 230 | // introduce race conditions, but the other `std.Build.Watch` implementations suffer from those | |
| 231 | // too at the time of writing, so this is kind of expected. | |
| 232 | fse.since_event = .since_now; | |
| 233 | ||
| 234 | const cf_allocator = rs.CFAllocatorCreate(rs.kCFAllocatorUseContext.*, &.{ | |
| 235 | .version = 0, | |
| 236 | .info = @constCast(&gpa), | |
| 237 | .retain = null, | |
| 238 | .release = null, | |
| 239 | .copy_description = null, | |
| 240 | .allocate = &cf_alloc_callbacks.allocate, | |
| 241 | .reallocate = &cf_alloc_callbacks.reallocate, | |
| 242 | .deallocate = &cf_alloc_callbacks.deallocate, | |
| 243 | .preferred_size = null, | |
| 244 | }) orelse return error.OutOfMemory; | |
| 245 | defer rs.CFRelease(cf_allocator); | |
| 246 | ||
| 247 | const cf_paths = try gpa.alloc(?CFStringRef, fse.watch_roots.len); | |
| 248 | @memset(cf_paths, null); | |
| 249 | defer { | |
| 250 | for (cf_paths) |o| if (o) |p| rs.CFRelease(p); | |
| 251 | gpa.free(cf_paths); | |
| 252 | } | |
| 253 | for (fse.watch_roots, cf_paths) |raw_path, *cf_path| { | |
| 254 | cf_path.* = rs.CFStringCreateWithCString(cf_allocator, raw_path, .utf8); | |
| 255 | } | |
| 256 | const cf_paths_array = rs.CFArrayCreate(cf_allocator, @ptrCast(cf_paths), @intCast(cf_paths.len), null); | |
| 257 | defer rs.CFRelease(cf_paths_array); | |
| 258 | ||
| 259 | const callback_ctx: EventCallbackCtx = .{ | |
| 260 | .fse = fse, | |
| 261 | .maker = maker, | |
| 262 | }; | |
| 263 | const event_stream = rs.FSEventStreamCreate( | |
| 264 | null, | |
| 265 | &eventCallback, | |
| 266 | &.{ | |
| 267 | .version = 0, | |
| 268 | .info = @constCast(&callback_ctx), | |
| 269 | .retain = null, | |
| 270 | .release = null, | |
| 271 | .copy_description = null, | |
| 272 | }, | |
| 273 | cf_paths_array, | |
| 274 | fse.since_event, | |
| 275 | 0.05, // 0.05s latency; higher values increase efficiency by coalescing more events | |
| 276 | .{ .watch_root = true, .file_events = true }, | |
| 277 | ); | |
| 278 | defer rs.FSEventStreamRelease(event_stream); | |
| 279 | rs.FSEventStreamSetDispatchQueue(event_stream, fse.dispatch_queue); | |
| 280 | defer rs.FSEventStreamInvalidate(event_stream); | |
| 281 | if (!rs.FSEventStreamStart(event_stream)) return error.StartFailed; | |
| 282 | defer rs.FSEventStreamStop(event_stream); | |
| 283 | const result = fse.waiting_semaphore.wait(timeout: { | |
| 284 | const ns = timeout_ns orelse break :timeout .FOREVER; | |
| 285 | break :timeout .time(.NOW, @intCast(ns)); | |
| 286 | }); | |
| 287 | return switch (result) { | |
| 288 | 0 => .dirty, | |
| 289 | else => .timeout, | |
| 290 | }; | |
| 291 | } | |
| 292 | ||
| 293 | const cf_alloc_callbacks = struct { | |
| 294 | const log = std.log.scoped(.cf_alloc); | |
| 295 | fn allocate(size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque { | |
| 296 | if (enable_debug_logs) log.debug("allocate {d}", .{size}); | |
| 297 | _ = hint; | |
| 298 | const gpa: *const Allocator = @ptrCast(@alignCast(info)); | |
| 299 | const mem = gpa.alignedAlloc(u8, .of(usize), @intCast(size + @sizeOf(usize))) catch return null; | |
| 300 | const metadata: *usize = @ptrCast(mem); | |
| 301 | metadata.* = @intCast(size); | |
| 302 | return mem[@sizeOf(usize)..].ptr; | |
| 303 | } | |
| 304 | fn reallocate(ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque { | |
| 305 | if (enable_debug_logs) log.debug("reallocate @{*} {d}", .{ ptr, new_size }); | |
| 306 | _ = hint; | |
| 307 | if (ptr == null or new_size == 0) return null; // not a bug: documentation explicitly states that realloc on NULL should return NULL | |
| 308 | const gpa: *const Allocator = @ptrCast(@alignCast(info)); | |
| 309 | const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize)); | |
| 310 | const old_size = @as(*const usize, @ptrCast(old_base)).*; | |
| 311 | const old_mem = old_base[0 .. old_size + @sizeOf(usize)]; | |
| 312 | const new_mem = gpa.realloc(old_mem, @intCast(new_size + @sizeOf(usize))) catch return null; | |
| 313 | const metadata: *usize = @ptrCast(new_mem); | |
| 314 | metadata.* = @intCast(new_size); | |
| 315 | return new_mem[@sizeOf(usize)..].ptr; | |
| 316 | } | |
| 317 | fn deallocate(ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void { | |
| 318 | if (enable_debug_logs) log.debug("deallocate @{*}", .{ptr}); | |
| 319 | const gpa: *const Allocator = @ptrCast(@alignCast(info)); | |
| 320 | const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize)); | |
| 321 | const old_size = @as(*const usize, @ptrCast(old_base)).*; | |
| 322 | const old_mem = old_base[0 .. old_size + @sizeOf(usize)]; | |
| 323 | gpa.free(old_mem); | |
| 324 | } | |
| 325 | }; | |
| 326 | ||
| 327 | const EventCallbackCtx = struct { | |
| 328 | fse: *FsEvents, | |
| 329 | maker: *Maker, | |
| 330 | }; | |
| 331 | ||
| 332 | fn eventCallback( | |
| 333 | stream: ConstFSEventStreamRef, | |
| 334 | client_callback_info: ?*anyopaque, | |
| 335 | num_events: usize, | |
| 336 | events_paths_ptr: *anyopaque, | |
| 337 | events_flags_ptr: [*]const FSEventStreamEventFlags, | |
| 338 | events_ids_ptr: [*]const FSEventStreamEventId, | |
| 339 | ) callconv(.c) void { | |
| 340 | const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info)); | |
| 341 | const maker = ctx.maker; | |
| 342 | const fse = ctx.fse; | |
| 343 | const rs = fse.resolved_symbols; | |
| 344 | const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr)); | |
| 345 | const events_paths = events_paths_ptr_casted[0..num_events]; | |
| 346 | const events_ids = events_ids_ptr[0..num_events]; | |
| 347 | const events_flags = events_flags_ptr[0..num_events]; | |
| 348 | var any_dirty = false; | |
| 349 | for (events_paths, events_ids, events_flags) |event_path_nts, event_id, event_flags| { | |
| 350 | _ = event_id; | |
| 351 | if (event_flags.history_done) continue; // sentinel | |
| 352 | const event_path = std.mem.span(event_path_nts); | |
| 353 | switch (event_flags.must_scan_sub_dirs) { | |
| 354 | false => { | |
| 355 | if (fse.watch_paths.get(event_path)) |steps| { | |
| 356 | assert(steps.len > 0); | |
| 357 | if (invalidateSteps(maker, steps)) any_dirty = true; | |
| 358 | } | |
| 359 | if (std.fs.path.dirname(event_path)) |event_dirname| { | |
| 360 | // Modifying '/foo/bar' triggers the watch on '/foo'. | |
| 361 | if (fse.watch_paths.get(event_dirname)) |steps| { | |
| 362 | assert(steps.len > 0); | |
| 363 | if (invalidateSteps(maker, steps)) any_dirty = true; | |
| 364 | } | |
| 365 | } | |
| 366 | }, | |
| 367 | true => { | |
| 368 | // This is unlikely, but can occasionally happen when bottlenecked: events have been | |
| 369 | // coalesced into one. We want to see if any of these events are actually relevant | |
| 370 | // to us. The only way we can reasonably do that in this rare edge case is iterate | |
| 371 | // the watch paths and see if any is under this directory. That's acceptable because | |
| 372 | // we would otherwise kick off a rebuild which would be clearing those paths anyway. | |
| 373 | const changed_path = std.fs.path.dirname(event_path) orelse event_path; | |
| 374 | for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| { | |
| 375 | if (dirStartsWith(watching_path, changed_path)) { | |
| 376 | if (invalidateSteps(maker, steps)) any_dirty = true; | |
| 377 | } | |
| 378 | } | |
| 379 | }, | |
| 380 | } | |
| 381 | } | |
| 382 | if (any_dirty) { | |
| 383 | fse.since_event = rs.FSEventStreamGetLatestEventId(stream); | |
| 384 | _ = fse.waiting_semaphore.signal(); | |
| 385 | } | |
| 386 | } | |
| 387 | fn dirStartsWith(path: []const u8, prefix: []const u8) bool { | |
| 388 | if (std.mem.eql(u8, path, prefix)) return true; | |
| 389 | if (!std.mem.startsWith(u8, path, prefix)) return false; | |
| 390 | if (path[prefix.len] != '/') return false; // `path` is `/foo/barx`, `prefix` is `/foo/bar` | |
| 391 | return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar` | |
| 392 | } | |
| 393 | ||
| 394 | fn invalidateSteps(maker: *Maker, steps: []const std.Build.Configuration.Step.Index) bool { | |
| 395 | var any_dirty = false; | |
| 396 | for (steps) |step_index| { | |
| 397 | const step = maker.stepByIndex(step_index); | |
| 398 | if (maker.invalidateResult(step)) any_dirty = true; | |
| 399 | } | |
| 400 | return any_dirty; | |
| 401 | } | |
| 402 | ||
| 403 | const CFAllocatorRef = ?*const opaque {}; | |
| 404 | const CFArrayRef = *const opaque {}; | |
| 405 | const CFStringRef = *const opaque {}; | |
| 406 | const CFTimeInterval = f64; | |
| 407 | const CFIndex = i32; | |
| 408 | const CFOptionFlags = enum(u32) { _ }; | |
| 409 | const CFAllocatorRetainCallBack = *const fn (info: ?*const anyopaque) callconv(.c) *const anyopaque; | |
| 410 | const CFAllocatorReleaseCallBack = *const fn (info: ?*const anyopaque) callconv(.c) void; | |
| 411 | const CFAllocatorCopyDescriptionCallBack = *const fn (info: ?*const anyopaque) callconv(.c) CFStringRef; | |
| 412 | const CFAllocatorAllocateCallBack = *const fn (alloc_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque; | |
| 413 | const CFAllocatorReallocateCallBack = *const fn (ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque; | |
| 414 | const CFAllocatorDeallocateCallBack = *const fn (ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void; | |
| 415 | const CFAllocatorPreferredSizeCallBack = *const fn (size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) CFIndex; | |
| 416 | const CFAllocatorContext = extern struct { | |
| 417 | version: CFIndex, | |
| 418 | info: ?*anyopaque, | |
| 419 | retain: ?CFAllocatorRetainCallBack, | |
| 420 | release: ?CFAllocatorReleaseCallBack, | |
| 421 | copy_description: ?CFAllocatorCopyDescriptionCallBack, | |
| 422 | allocate: CFAllocatorAllocateCallBack, | |
| 423 | reallocate: ?CFAllocatorReallocateCallBack, | |
| 424 | deallocate: ?CFAllocatorDeallocateCallBack, | |
| 425 | preferred_size: ?CFAllocatorPreferredSizeCallBack, | |
| 426 | }; | |
| 427 | const CFArrayCallBacks = opaque {}; | |
| 428 | const CFStringEncoding = enum(u32) { | |
| 429 | invalid_id = std.math.maxInt(u32), | |
| 430 | mac_roman = 0, | |
| 431 | windows_latin_1 = 0x500, | |
| 432 | iso_latin_1 = 0x201, | |
| 433 | next_step_latin = 0xB01, | |
| 434 | ascii = 0x600, | |
| 435 | unicode = 0x100, | |
| 436 | utf8 = 0x8000100, | |
| 437 | non_lossy_ascii = 0xBFF, | |
| 438 | }; | |
| 439 | ||
| 440 | const FSEventStreamRef = *opaque {}; | |
| 441 | const ConstFSEventStreamRef = *const @typeInfo(FSEventStreamRef).pointer.child; | |
| 442 | const FSEventStreamCallback = *const fn ( | |
| 443 | stream: ConstFSEventStreamRef, | |
| 444 | client_callback_info: ?*anyopaque, | |
| 445 | num_events: usize, | |
| 446 | event_paths: *anyopaque, | |
| 447 | event_flags: [*]const FSEventStreamEventFlags, | |
| 448 | event_ids: [*]const FSEventStreamEventId, | |
| 449 | ) callconv(.c) void; | |
| 450 | const FSEventStreamContext = extern struct { | |
| 451 | version: CFIndex, | |
| 452 | info: ?*anyopaque, | |
| 453 | retain: ?CFAllocatorRetainCallBack, | |
| 454 | release: ?CFAllocatorReleaseCallBack, | |
| 455 | copy_description: ?CFAllocatorCopyDescriptionCallBack, | |
| 456 | }; | |
| 457 | const FSEventStreamEventId = enum(u64) { | |
| 458 | since_now = std.math.maxInt(u64), | |
| 459 | _, | |
| 460 | }; | |
| 461 | const FSEventStreamCreateFlags = packed struct(u32) { | |
| 462 | use_cf_types: bool = false, | |
| 463 | no_defer: bool = false, | |
| 464 | watch_root: bool = false, | |
| 465 | ignore_self: bool = false, | |
| 466 | file_events: bool = false, | |
| 467 | _: u27 = 0, | |
| 468 | }; | |
| 469 | const FSEventStreamEventFlags = packed struct(u32) { | |
| 470 | must_scan_sub_dirs: bool, | |
| 471 | user_dropped: bool, | |
| 472 | kernel_dropped: bool, | |
| 473 | event_ids_wrapped: bool, | |
| 474 | history_done: bool, | |
| 475 | root_changed: bool, | |
| 476 | mount: bool, | |
| 477 | unmount: bool, | |
| 478 | _: u24 = 0, | |
| 479 | }; | |
| 480 | ||
| 481 | const dispatch = std.c.dispatch; | |
| 482 | const std = @import("std"); | |
| 483 | const Io = std.Io; | |
| 484 | const assert = std.debug.assert; | |
| 485 | const Allocator = std.mem.Allocator; | |
| 486 | const watch_log = std.log.scoped(.watch); | |
| 487 | const Maker = @import("../../Maker.zig"); | |
| 488 | const Watch = @import("../Watch.zig"); |
lib/compiler/Maker/WebServer.zig created+953| ... | ... | @@ -0,0 +1,953 @@ |
| 1 | const WebServer = @This(); | |
| 2 | ||
| 3 | const builtin = @import("builtin"); | |
| 4 | ||
| 5 | const std = @import("std"); | |
| 6 | const Allocator = std.mem.Allocator; | |
| 7 | const Cache = std.Build.Cache; | |
| 8 | const Configuration = std.Build.Configuration; | |
| 9 | const Io = std.Io; | |
| 10 | const abi = std.Build.abi; | |
| 11 | const assert = std.debug.assert; | |
| 12 | const http = std.http; | |
| 13 | const log = std.log.scoped(.web_server); | |
| 14 | const mem = std.mem; | |
| 15 | const net = std.Io.net; | |
| 16 | ||
| 17 | const Maker = @import("../Maker.zig"); | |
| 18 | const Fuzz = @import("Fuzz.zig"); | |
| 19 | const Graph = @import("Graph.zig"); | |
| 20 | const Step = @import("Step.zig"); | |
| 21 | ||
| 22 | maker: *Maker, | |
| 23 | listen_address: net.IpAddress, | |
| 24 | root_prog_node: std.Progress.Node, | |
| 25 | ||
| 26 | tcp_server: ?net.Server, | |
| 27 | serve_task: ?Io.Future(Io.Cancelable!void), | |
| 28 | ||
| 29 | /// Uses `Io.Clock.awake`. | |
| 30 | base_timestamp: Io.Timestamp, | |
| 31 | /// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`. | |
| 32 | step_names_trailing: []u8, | |
| 33 | ||
| 34 | /// The bit-packed "step status" data. Values are `abi.StepUpdate.Status`. LSBs are earlier steps. | |
| 35 | /// Accessed atomically. | |
| 36 | step_status_bits: []u8, | |
| 37 | ||
| 38 | fuzz: ?Fuzz, | |
| 39 | time_report_mutex: Io.Mutex, | |
| 40 | time_report_msgs: [][]u8, | |
| 41 | time_report_update_times: []i64, | |
| 42 | ||
| 43 | build_status: std.atomic.Value(abi.BuildStatus), | |
| 44 | /// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate` | |
| 45 | /// to increment this value. Each client thread waits for this increment with `Io.futexWaitTimeout`, so | |
| 46 | /// `notifyUpdate` will wake those threads. Updates are sent on a short interval regardless, so it | |
| 47 | /// is recommended to only use `notifyUpdate` for changes which the user should see immediately. For | |
| 48 | /// instance, we do not call `notifyUpdate` when the number of "unique runs" in the fuzzer changes, | |
| 49 | /// because this value changes quickly so this would result in constantly spamming all clients with | |
| 50 | /// an unreasonable number of packets. | |
| 51 | update_id: std.atomic.Value(u32), | |
| 52 | ||
| 53 | runner_request_mutex: Io.Mutex, | |
| 54 | runner_request_ready_cond: Io.Condition, | |
| 55 | runner_request_empty_cond: Io.Condition, | |
| 56 | runner_request: ?RunnerRequest, | |
| 57 | ||
| 58 | /// If a client is not explicitly notified of changes with `notifyUpdate`, it will be sent updates | |
| 59 | /// on a fixed interval of this many milliseconds. | |
| 60 | const default_update_interval_ms = 500; | |
| 61 | ||
| 62 | pub const base_clock: Io.Clock = .awake; | |
| 63 | ||
| 64 | /// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`. | |
| 65 | pub fn notifyUpdate(ws: *WebServer) void { | |
| 66 | const io = ws.maker.graph.io; | |
| 67 | _ = ws.update_id.rmw(.Add, 1, .release); | |
| 68 | io.futexWake(u32, &ws.update_id.raw, 16); | |
| 69 | } | |
| 70 | ||
| 71 | pub const Options = struct { | |
| 72 | maker: *Maker, | |
| 73 | root_prog_node: std.Progress.Node, | |
| 74 | listen_address: net.IpAddress, | |
| 75 | base_timestamp: Io.Clock.Timestamp, | |
| 76 | }; | |
| 77 | pub fn init(opts: Options) WebServer { | |
| 78 | // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent` | |
| 79 | // instead of threads, so that the web server can function in single-threaded builds. | |
| 80 | comptime assert(!builtin.single_threaded); | |
| 81 | assert(opts.base_timestamp.clock == base_clock); | |
| 82 | ||
| 83 | const maker = opts.maker; | |
| 84 | const all_steps = maker.step_stack.keys(); | |
| 85 | const c = &maker.scanned_config.configuration; | |
| 86 | const gpa = maker.gpa; | |
| 87 | const graph = maker.graph; | |
| 88 | ||
| 89 | const step_names_trailing = gpa.alloc(u8, len: { | |
| 90 | var name_bytes: usize = 0; | |
| 91 | for (all_steps) |step_index| name_bytes += step_index.ptr(c).name.slice(c).len; | |
| 92 | break :len name_bytes + all_steps.len * 4; | |
| 93 | }) catch @panic("out of memory"); | |
| 94 | { | |
| 95 | const step_name_lens: []align(1) u32 = @ptrCast(step_names_trailing[0 .. all_steps.len * 4]); | |
| 96 | var idx: usize = all_steps.len * 4; | |
| 97 | for (all_steps, step_name_lens) |step_index, *name_len| { | |
| 98 | const step_name = step_index.ptr(c).name.slice(c); | |
| 99 | name_len.* = @intCast(step_name.len); | |
| 100 | @memcpy(step_names_trailing[idx..][0..step_name.len], step_name); | |
| 101 | idx += step_name.len; | |
| 102 | } | |
| 103 | assert(idx == step_names_trailing.len); | |
| 104 | } | |
| 105 | ||
| 106 | const step_status_bits = gpa.alloc( | |
| 107 | u8, | |
| 108 | std.math.divCeil(usize, all_steps.len, 4) catch unreachable, | |
| 109 | ) catch @panic("out of memory"); | |
| 110 | @memset(step_status_bits, 0); | |
| 111 | ||
| 112 | const time_reports_len: usize = if (graph.time_report) all_steps.len else 0; | |
| 113 | const time_report_msgs = gpa.alloc([]u8, time_reports_len) catch @panic("out of memory"); | |
| 114 | const time_report_update_times = gpa.alloc(i64, time_reports_len) catch @panic("out of memory"); | |
| 115 | @memset(time_report_msgs, &.{}); | |
| 116 | @memset(time_report_update_times, std.math.minInt(i64)); | |
| 117 | ||
| 118 | return .{ | |
| 119 | .maker = maker, | |
| 120 | .listen_address = opts.listen_address, | |
| 121 | .root_prog_node = opts.root_prog_node, | |
| 122 | ||
| 123 | .tcp_server = null, | |
| 124 | .serve_task = null, | |
| 125 | ||
| 126 | .base_timestamp = opts.base_timestamp.raw, | |
| 127 | .step_names_trailing = step_names_trailing, | |
| 128 | ||
| 129 | .step_status_bits = step_status_bits, | |
| 130 | ||
| 131 | .fuzz = null, | |
| 132 | .time_report_mutex = .init, | |
| 133 | .time_report_msgs = time_report_msgs, | |
| 134 | .time_report_update_times = time_report_update_times, | |
| 135 | ||
| 136 | .build_status = .init(.idle), | |
| 137 | .update_id = .init(0), | |
| 138 | ||
| 139 | .runner_request_mutex = .init, | |
| 140 | .runner_request_ready_cond = .init, | |
| 141 | .runner_request_empty_cond = .init, | |
| 142 | .runner_request = null, | |
| 143 | }; | |
| 144 | } | |
| 145 | pub fn deinit(ws: *WebServer) void { | |
| 146 | const maker = ws.maker; | |
| 147 | const gpa = maker.gpa; | |
| 148 | const io = maker.graph.io; | |
| 149 | ||
| 150 | gpa.free(ws.step_names_trailing); | |
| 151 | gpa.free(ws.step_status_bits); | |
| 152 | ||
| 153 | if (ws.fuzz) |*f| f.deinit(); | |
| 154 | for (ws.time_report_msgs) |msg| gpa.free(msg); | |
| 155 | gpa.free(ws.time_report_msgs); | |
| 156 | gpa.free(ws.time_report_update_times); | |
| 157 | ||
| 158 | if (ws.serve_task) |t| { | |
| 159 | if (ws.tcp_server) |*s| s.stream.close(io); | |
| 160 | t.await(); | |
| 161 | } | |
| 162 | if (ws.tcp_server) |*s| s.deinit(); | |
| 163 | ||
| 164 | gpa.free(ws.step_names_trailing); | |
| 165 | } | |
| 166 | pub fn start(ws: *WebServer) error{AlreadyReported}!void { | |
| 167 | assert(ws.tcp_server == null); | |
| 168 | assert(ws.serve_task == null); | |
| 169 | const maker = ws.maker; | |
| 170 | const io = maker.graph.io; | |
| 171 | ||
| 172 | ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| { | |
| 173 | log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err }); | |
| 174 | return error.AlreadyReported; | |
| 175 | }; | |
| 176 | ws.serve_task = io.concurrent(serve, .{ws}) catch |err| { | |
| 177 | log.err("unable to spawn web server thread: {t}", .{err}); | |
| 178 | ws.tcp_server.?.deinit(io); | |
| 179 | ws.tcp_server = null; | |
| 180 | return error.AlreadyReported; | |
| 181 | }; | |
| 182 | ||
| 183 | log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.socket.address}); | |
| 184 | if (ws.listen_address.getPort() == 0) { | |
| 185 | log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address}); | |
| 186 | } | |
| 187 | } | |
| 188 | fn serve(ws: *WebServer) Io.Cancelable!void { | |
| 189 | const maker = ws.maker; | |
| 190 | const io = maker.graph.io; | |
| 191 | ||
| 192 | var group: Io.Group = .init; | |
| 193 | defer group.cancel(io); | |
| 194 | ||
| 195 | while (true) { | |
| 196 | var stream = ws.tcp_server.?.accept(io) catch |err| switch (err) { | |
| 197 | error.Canceled => |e| return e, | |
| 198 | else => |e| { | |
| 199 | log.err("failed to accept connection: {t}", .{e}); | |
| 200 | return; | |
| 201 | }, | |
| 202 | }; | |
| 203 | group.concurrent(io, accept, .{ ws, stream }) catch |err| { | |
| 204 | log.err("unable to spawn connection thread: {t}", .{err}); | |
| 205 | stream.close(io); | |
| 206 | continue; | |
| 207 | }; | |
| 208 | } | |
| 209 | } | |
| 210 | ||
| 211 | pub fn startBuild(ws: *WebServer) void { | |
| 212 | if (ws.fuzz) |*fuzz| { | |
| 213 | fuzz.deinit(); | |
| 214 | ws.fuzz = null; | |
| 215 | } | |
| 216 | for (ws.step_status_bits) |*bits| @atomicStore(u8, bits, 0, .monotonic); | |
| 217 | ws.build_status.store(.running, .monotonic); | |
| 218 | ws.notifyUpdate(); | |
| 219 | } | |
| 220 | ||
| 221 | pub fn updateStepStatus( | |
| 222 | ws: *WebServer, | |
| 223 | step_index: Configuration.Step.Index, | |
| 224 | new_status: abi.StepUpdate.Status, | |
| 225 | ) void { | |
| 226 | const maker = ws.maker; | |
| 227 | const all_steps = maker.step_stack.keys(); | |
| 228 | const step_idx: u32 = for (all_steps, 0..) |s, i| { | |
| 229 | if (s == step_index) break @intCast(i); | |
| 230 | } else unreachable; | |
| 231 | const ptr = &ws.step_status_bits[step_idx / 4]; | |
| 232 | const bit_offset: u3 = @intCast((step_idx % 4) * 2); | |
| 233 | const old_bits: u2 = @truncate(@atomicLoad(u8, ptr, .monotonic) >> bit_offset); | |
| 234 | const mask = @as(u8, @intFromEnum(new_status) ^ old_bits) << bit_offset; | |
| 235 | _ = @atomicRmw(u8, ptr, .Xor, mask, .monotonic); | |
| 236 | ws.notifyUpdate(); | |
| 237 | } | |
| 238 | ||
| 239 | pub fn finishBuild(ws: *WebServer, opts: struct { | |
| 240 | fuzz: bool, | |
| 241 | }) void { | |
| 242 | const maker = ws.maker; | |
| 243 | const all_steps = maker.step_stack.keys(); | |
| 244 | ||
| 245 | if (opts.fuzz) { | |
| 246 | switch (builtin.os.tag) { | |
| 247 | // Current implementation depends on two things that need to be ported to Windows: | |
| 248 | // * Memory-mapping to share data between the fuzzer and build runner. | |
| 249 | // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving | |
| 250 | // many addresses to source locations). | |
| 251 | .windows => std.process.fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}), | |
| 252 | else => {}, | |
| 253 | } | |
| 254 | if (@bitSizeOf(usize) != 64) { | |
| 255 | // Current implementation depends on posix.mmap()'s second | |
| 256 | // parameter, `length: usize`, being compatible with file system's | |
| 257 | // u64 return value. This is not the case on 32-bit platforms. | |
| 258 | // Affects or affected by issues #5185, #22523, and #22464. | |
| 259 | std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); | |
| 260 | } | |
| 261 | ||
| 262 | assert(ws.fuzz == null); | |
| 263 | ||
| 264 | ws.build_status.store(.fuzz_init, .monotonic); | |
| 265 | ws.notifyUpdate(); | |
| 266 | ||
| 267 | ws.fuzz = Fuzz.init(maker, all_steps, ws.root_prog_node, .{ .forever = .{ .ws = ws } }) catch |err| | |
| 268 | std.process.fatal("failed to start fuzzer: {t}", .{err}); | |
| 269 | ws.fuzz.?.start(); | |
| 270 | } | |
| 271 | ||
| 272 | ws.build_status.store(if (maker.watch) .watching else .idle, .monotonic); | |
| 273 | ws.notifyUpdate(); | |
| 274 | } | |
| 275 | ||
| 276 | pub fn now(ws: *const WebServer) i64 { | |
| 277 | const maker = ws.maker; | |
| 278 | const io = maker.graph.io; | |
| 279 | const ts = base_clock.now(io); | |
| 280 | return @intCast(ws.base_timestamp.durationTo(ts).toNanoseconds()); | |
| 281 | } | |
| 282 | ||
| 283 | fn accept(ws: *WebServer, stream: net.Stream) void { | |
| 284 | const maker = ws.maker; | |
| 285 | const io = maker.graph.io; | |
| 286 | ||
| 287 | defer { | |
| 288 | // `net.Stream.close` wants to helpfully overwrite `stream` with | |
| 289 | // `undefined`, but it cannot do so since it is an immutable parameter. | |
| 290 | var copy = stream; | |
| 291 | copy.close(io); | |
| 292 | } | |
| 293 | var send_buffer: [4096]u8 = undefined; | |
| 294 | var recv_buffer: [4096]u8 = undefined; | |
| 295 | var connection_reader = stream.reader(io, &recv_buffer); | |
| 296 | var connection_writer = stream.writer(io, &send_buffer); | |
| 297 | var server: http.Server = .init(&connection_reader.interface, &connection_writer.interface); | |
| 298 | ||
| 299 | while (true) { | |
| 300 | var request = server.receiveHead() catch |err| switch (err) { | |
| 301 | error.HttpConnectionClosing => return, | |
| 302 | else => return log.err("failed to receive http request: {t}", .{err}), | |
| 303 | }; | |
| 304 | switch (request.upgradeRequested()) { | |
| 305 | .websocket => |opt_key| { | |
| 306 | const key = opt_key orelse return log.err("missing websocket key", .{}); | |
| 307 | var web_socket = request.respondWebSocket(.{ .key = key }) catch { | |
| 308 | return log.err("failed to respond web socket: {t}", .{connection_writer.err.?}); | |
| 309 | }; | |
| 310 | ws.serveWebSocket(&web_socket) catch |err| { | |
| 311 | log.err("failed to serve websocket: {t}", .{err}); | |
| 312 | return; | |
| 313 | }; | |
| 314 | comptime unreachable; | |
| 315 | }, | |
| 316 | .other => |name| return log.err("unknown upgrade request: {s}", .{name}), | |
| 317 | .none => { | |
| 318 | ws.serveRequest(&request) catch |err| switch (err) { | |
| 319 | error.AlreadyReported => return, | |
| 320 | else => { | |
| 321 | log.err("failed to serve '{s}': {t}", .{ request.head.target, err }); | |
| 322 | return; | |
| 323 | }, | |
| 324 | }; | |
| 325 | }, | |
| 326 | } | |
| 327 | } | |
| 328 | } | |
| 329 | ||
| 330 | fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { | |
| 331 | const maker = ws.maker; | |
| 332 | const gpa = maker.gpa; | |
| 333 | const graph = maker.graph; | |
| 334 | const io = graph.io; | |
| 335 | const all_steps = maker.step_stack.keys(); | |
| 336 | ||
| 337 | var prev_build_status = ws.build_status.load(.monotonic); | |
| 338 | ||
| 339 | const prev_step_status_bits = try gpa.alloc(u8, ws.step_status_bits.len); | |
| 340 | defer gpa.free(prev_step_status_bits); | |
| 341 | for (prev_step_status_bits, ws.step_status_bits) |*copy, *shared| { | |
| 342 | copy.* = @atomicLoad(u8, shared, .monotonic); | |
| 343 | } | |
| 344 | ||
| 345 | var recv_thread = try io.concurrent(recvWebSocketMessages, .{ ws, sock }); | |
| 346 | defer recv_thread.cancel(io); | |
| 347 | ||
| 348 | { | |
| 349 | const hello_header: abi.Hello = .{ | |
| 350 | .status = prev_build_status, | |
| 351 | .flags = .{ | |
| 352 | .time_report = graph.time_report, | |
| 353 | }, | |
| 354 | .timestamp = ws.now(), | |
| 355 | .steps_len = @intCast(all_steps.len), | |
| 356 | }; | |
| 357 | var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits }; | |
| 358 | try sock.writeMessageVec(&bufs, .binary); | |
| 359 | } | |
| 360 | ||
| 361 | var prev_fuzz: Fuzz.Previous = .init; | |
| 362 | var prev_time: i64 = std.math.minInt(i64); | |
| 363 | while (true) { | |
| 364 | const start_time = ws.now(); | |
| 365 | const start_update_id = ws.update_id.load(.acquire); | |
| 366 | ||
| 367 | if (ws.fuzz) |*fuzz| { | |
| 368 | try fuzz.sendUpdate(sock, &prev_fuzz); | |
| 369 | } | |
| 370 | ||
| 371 | { | |
| 372 | try ws.time_report_mutex.lock(io); | |
| 373 | defer ws.time_report_mutex.unlock(io); | |
| 374 | for (ws.time_report_msgs, ws.time_report_update_times) |msg, update_time| { | |
| 375 | if (update_time <= prev_time) continue; | |
| 376 | // We want to send `msg`, but shouldn't block `ws.time_report_mutex` while we do, so | |
| 377 | // that we don't hold up the build system on the client accepting this packet. | |
| 378 | const owned_msg = try gpa.dupe(u8, msg); | |
| 379 | defer gpa.free(owned_msg); | |
| 380 | // Temporarily unlock, then re-lock after the message is sent. | |
| 381 | ws.time_report_mutex.unlock(io); | |
| 382 | defer ws.time_report_mutex.lockUncancelable(io); | |
| 383 | try sock.writeMessage(owned_msg, .binary); | |
| 384 | } | |
| 385 | } | |
| 386 | ||
| 387 | { | |
| 388 | const build_status = ws.build_status.load(.monotonic); | |
| 389 | if (build_status != prev_build_status) { | |
| 390 | prev_build_status = build_status; | |
| 391 | const msg: abi.StatusUpdate = .{ .new = build_status }; | |
| 392 | try sock.writeMessage(@ptrCast(&msg), .binary); | |
| 393 | } | |
| 394 | } | |
| 395 | ||
| 396 | for (prev_step_status_bits, ws.step_status_bits, 0..) |*prev_byte, *shared, byte_idx| { | |
| 397 | const cur_byte = @atomicLoad(u8, shared, .monotonic); | |
| 398 | if (prev_byte.* == cur_byte) continue; | |
| 399 | const cur: [4]abi.StepUpdate.Status = .{ | |
| 400 | @enumFromInt(@as(u2, @truncate(cur_byte >> 0))), | |
| 401 | @enumFromInt(@as(u2, @truncate(cur_byte >> 2))), | |
| 402 | @enumFromInt(@as(u2, @truncate(cur_byte >> 4))), | |
| 403 | @enumFromInt(@as(u2, @truncate(cur_byte >> 6))), | |
| 404 | }; | |
| 405 | const prev: [4]abi.StepUpdate.Status = .{ | |
| 406 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 0))), | |
| 407 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 2))), | |
| 408 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 4))), | |
| 409 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 6))), | |
| 410 | }; | |
| 411 | for (cur, prev, byte_idx * 4..) |cur_status, prev_status, step_idx| { | |
| 412 | const msg: abi.StepUpdate = .{ .step_idx = @intCast(step_idx), .bits = .{ .status = cur_status } }; | |
| 413 | if (cur_status != prev_status) try sock.writeMessage(@ptrCast(&msg), .binary); | |
| 414 | } | |
| 415 | prev_byte.* = cur_byte; | |
| 416 | } | |
| 417 | ||
| 418 | prev_time = start_time; | |
| 419 | ||
| 420 | const old_cp = io.swapCancelProtection(.blocked); | |
| 421 | defer _ = io.swapCancelProtection(old_cp); | |
| 422 | io.futexWaitTimeout( | |
| 423 | u32, | |
| 424 | &ws.update_id.raw, | |
| 425 | start_update_id, | |
| 426 | .{ .duration = .{ | |
| 427 | .clock = .awake, | |
| 428 | .raw = .fromMilliseconds(default_update_interval_ms), | |
| 429 | } }, | |
| 430 | ) catch |err| switch (err) { | |
| 431 | error.Canceled => unreachable, | |
| 432 | }; | |
| 433 | } | |
| 434 | } | |
| 435 | fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void { | |
| 436 | const maker = ws.maker; | |
| 437 | const io = maker.graph.io; | |
| 438 | ||
| 439 | while (true) { | |
| 440 | const msg = sock.readSmallMessage() catch return; | |
| 441 | if (msg.opcode != .binary) continue; | |
| 442 | if (msg.data.len == 0) continue; | |
| 443 | const tag: abi.ToServerTag = @enumFromInt(msg.data[0]); | |
| 444 | switch (tag) { | |
| 445 | _ => continue, | |
| 446 | .rebuild => while (true) { | |
| 447 | ws.runner_request_mutex.lock(io) catch |err| switch (err) { | |
| 448 | error.Canceled => return, | |
| 449 | }; | |
| 450 | defer ws.runner_request_mutex.unlock(io); | |
| 451 | if (ws.runner_request == null) { | |
| 452 | ws.runner_request = .rebuild; | |
| 453 | ws.runner_request_ready_cond.signal(io); | |
| 454 | break; | |
| 455 | } | |
| 456 | ws.runner_request_empty_cond.wait(io, &ws.runner_request_mutex) catch return; | |
| 457 | }, | |
| 458 | } | |
| 459 | } | |
| 460 | } | |
| 461 | ||
| 462 | fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void { | |
| 463 | // Strip an optional leading '/debug' component from the request. | |
| 464 | const target: []const u8, const debug: bool = target: { | |
| 465 | if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true }; | |
| 466 | if (mem.eql(u8, req.head.target, "/debug/")) break :target .{ "/", true }; | |
| 467 | if (mem.startsWith(u8, req.head.target, "/debug/")) break :target .{ req.head.target["/debug".len..], true }; | |
| 468 | break :target .{ req.head.target, false }; | |
| 469 | }; | |
| 470 | ||
| 471 | if (mem.eql(u8, target, "/")) return serveLibFile(ws, req, "build-web/index.html", "text/html"); | |
| 472 | if (mem.eql(u8, target, "/main.js")) return serveLibFile(ws, req, "build-web/main.js", "application/javascript"); | |
| 473 | if (mem.eql(u8, target, "/style.css")) return serveLibFile(ws, req, "build-web/style.css", "text/css"); | |
| 474 | if (mem.eql(u8, target, "/time_report.css")) return serveLibFile(ws, req, "build-web/time_report.css", "text/css"); | |
| 475 | if (mem.eql(u8, target, "/main.wasm")) return serveClientWasm(ws, req, if (debug) .Debug else .ReleaseFast); | |
| 476 | ||
| 477 | if (ws.fuzz) |*fuzz| { | |
| 478 | if (mem.eql(u8, target, "/sources.tar")) return fuzz.serveSourcesTar(req); | |
| 479 | } | |
| 480 | ||
| 481 | try req.respond("not found", .{ | |
| 482 | .status = .not_found, | |
| 483 | .extra_headers = &.{ | |
| 484 | .{ .name = "Content-Type", .value = "text/plain" }, | |
| 485 | }, | |
| 486 | }); | |
| 487 | } | |
| 488 | ||
| 489 | fn serveLibFile( | |
| 490 | ws: *WebServer, | |
| 491 | request: *http.Server.Request, | |
| 492 | sub_path: []const u8, | |
| 493 | content_type: []const u8, | |
| 494 | ) !void { | |
| 495 | const maker = ws.maker; | |
| 496 | const graph = maker.graph; | |
| 497 | ||
| 498 | return serveFile(ws, request, .{ | |
| 499 | .root_dir = graph.zig_lib_directory, | |
| 500 | .sub_path = sub_path, | |
| 501 | }, content_type); | |
| 502 | } | |
| 503 | fn serveClientWasm( | |
| 504 | ws: *WebServer, | |
| 505 | req: *http.Server.Request, | |
| 506 | optimize_mode: std.builtin.OptimizeMode, | |
| 507 | ) !void { | |
| 508 | const gpa = ws.maker.gpa; | |
| 509 | ||
| 510 | var arena_state: std.heap.ArenaAllocator = .init(gpa); | |
| 511 | defer arena_state.deinit(); | |
| 512 | const arena = arena_state.allocator(); | |
| 513 | ||
| 514 | // We always rebuild the wasm on-the-fly, so that if it is edited the user can just refresh the page. | |
| 515 | const bin_path = try buildClientWasm(ws, arena, optimize_mode); | |
| 516 | return serveFile(ws, req, bin_path, "application/wasm"); | |
| 517 | } | |
| 518 | ||
| 519 | pub fn serveFile( | |
| 520 | ws: *WebServer, | |
| 521 | request: *http.Server.Request, | |
| 522 | path: Cache.Path, | |
| 523 | content_type: []const u8, | |
| 524 | ) !void { | |
| 525 | const maker = ws.maker; | |
| 526 | const gpa = ws.maker.gpa; | |
| 527 | const io = maker.graph.io; | |
| 528 | ||
| 529 | // The desired API is actually sendfile, which will require enhancing http.Server. | |
| 530 | // We load the file with every request so that the user can make changes to the file | |
| 531 | // and refresh the HTML page without restarting this server. | |
| 532 | const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| { | |
| 533 | log.err("failed to read '{f}': {t}", .{ path, err }); | |
| 534 | return error.AlreadyReported; | |
| 535 | }; | |
| 536 | defer gpa.free(file_contents); | |
| 537 | try request.respond(file_contents, .{ | |
| 538 | .extra_headers = &.{ | |
| 539 | .{ .name = "Content-Type", .value = content_type }, | |
| 540 | cache_control_header, | |
| 541 | }, | |
| 542 | }); | |
| 543 | } | |
| 544 | pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void { | |
| 545 | const maker = ws.maker; | |
| 546 | const graph = maker.graph; | |
| 547 | const io = graph.io; | |
| 548 | ||
| 549 | var send_buffer: [0x4000]u8 = undefined; | |
| 550 | var response = try request.respondStreaming(&send_buffer, .{ | |
| 551 | .respond_options = .{ | |
| 552 | .extra_headers = &.{ | |
| 553 | .{ .name = "Content-Type", .value = "application/x-tar" }, | |
| 554 | cache_control_header, | |
| 555 | }, | |
| 556 | }, | |
| 557 | }); | |
| 558 | ||
| 559 | var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; | |
| 560 | ||
| 561 | for (paths) |path| { | |
| 562 | var file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| { | |
| 563 | log.err("failed to open '{f}': {s}", .{ path, @errorName(err) }); | |
| 564 | continue; | |
| 565 | }; | |
| 566 | defer file.close(io); | |
| 567 | const stat = try file.stat(io); | |
| 568 | var read_buffer: [1024]u8 = undefined; | |
| 569 | var file_reader: Io.File.Reader = .initSize(file, io, &read_buffer, stat.size); | |
| 570 | ||
| 571 | archiver.prefix = path.root_dir.path orelse graph.cache.cwd; | |
| 572 | try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); | |
| 573 | } | |
| 574 | ||
| 575 | // intentionally not calling `archiver.finishPedantically` | |
| 576 | try response.end(); | |
| 577 | } | |
| 578 | ||
| 579 | fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path { | |
| 580 | const root_name = "build-web"; | |
| 581 | const arch_os_abi = "wasm32-freestanding"; | |
| 582 | const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; | |
| 583 | ||
| 584 | const maker = ws.maker; | |
| 585 | const gpa = maker.gpa; | |
| 586 | const graph = maker.graph; | |
| 587 | const io = graph.io; | |
| 588 | ||
| 589 | const main_src_path: Cache.Path = .{ | |
| 590 | .root_dir = graph.zig_lib_directory, | |
| 591 | .sub_path = "build-web/main.zig", | |
| 592 | }; | |
| 593 | const walk_src_path: Cache.Path = .{ | |
| 594 | .root_dir = graph.zig_lib_directory, | |
| 595 | .sub_path = "docs/wasm/Walk.zig", | |
| 596 | }; | |
| 597 | const html_render_src_path: Cache.Path = .{ | |
| 598 | .root_dir = graph.zig_lib_directory, | |
| 599 | .sub_path = "docs/wasm/html_render.zig", | |
| 600 | }; | |
| 601 | ||
| 602 | var argv: std.ArrayList([]const u8) = .empty; | |
| 603 | ||
| 604 | try argv.appendSlice(arena, &.{ | |
| 605 | graph.zig_exe, "build-exe", // | |
| 606 | "-fno-entry", // | |
| 607 | "-O", @tagName(optimize), // | |
| 608 | "-target", arch_os_abi, // | |
| 609 | "-mcpu", cpu_features, // | |
| 610 | "--cache-dir", graph.global_cache_root.path orelse ".", // | |
| 611 | "--global-cache-dir", graph.global_cache_root.path orelse ".", // | |
| 612 | "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", // | |
| 613 | "--name", root_name, // | |
| 614 | "-rdynamic", // | |
| 615 | "-fsingle-threaded", // | |
| 616 | "--dep", "Walk", // | |
| 617 | "--dep", "html_render", // | |
| 618 | try std.fmt.allocPrint(arena, "-Mroot={f}", .{main_src_path}), // | |
| 619 | try std.fmt.allocPrint(arena, "-MWalk={f}", .{walk_src_path}), // | |
| 620 | "--dep", "Walk", // | |
| 621 | try std.fmt.allocPrint(arena, "-Mhtml_render={f}", .{html_render_src_path}), // | |
| 622 | "--listen=-", | |
| 623 | }); | |
| 624 | ||
| 625 | var child = try std.process.spawn(io, .{ | |
| 626 | .argv = argv.items, | |
| 627 | .environ_map = &graph.environ_map, | |
| 628 | .stdin = .pipe, | |
| 629 | .stdout = .pipe, | |
| 630 | .stderr = .pipe, | |
| 631 | }); | |
| 632 | defer child.kill(io); | |
| 633 | ||
| 634 | var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited }); | |
| 635 | defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {}; | |
| 636 | ||
| 637 | var stdout_buffer: [512]u8 = undefined; | |
| 638 | var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer); | |
| 639 | const stdout = &stdout_reader.interface; | |
| 640 | ||
| 641 | { | |
| 642 | var w = child.stdin.?.writer(io, &.{}); | |
| 643 | w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .update, .bytes_len = 0 }, .little) catch |err| switch (err) { | |
| 644 | error.WriteFailed => return w.err.?, | |
| 645 | }; | |
| 646 | w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .exit, .bytes_len = 0 }, .little) catch |err| switch (err) { | |
| 647 | error.WriteFailed => return w.err.?, | |
| 648 | }; | |
| 649 | } | |
| 650 | ||
| 651 | const Header = std.zig.Server.Message.Header; | |
| 652 | ||
| 653 | var result: ?Cache.Path = null; | |
| 654 | var result_error_bundle = std.zig.ErrorBundle.empty; | |
| 655 | var body_buffer: std.ArrayList(u8) = .empty; | |
| 656 | defer body_buffer.deinit(gpa); | |
| 657 | ||
| 658 | while (true) { | |
| 659 | const header = stdout.takeStruct(Header, .little) catch |err| switch (err) { | |
| 660 | error.ReadFailed => |e| return e, | |
| 661 | error.EndOfStream => break, | |
| 662 | }; | |
| 663 | body_buffer.clearRetainingCapacity(); | |
| 664 | try stdout.appendExact(gpa, &body_buffer, header.bytes_len); | |
| 665 | const body = body_buffer.items; | |
| 666 | ||
| 667 | switch (header.tag) { | |
| 668 | .zig_version => { | |
| 669 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { | |
| 670 | return error.ZigProtocolVersionMismatch; | |
| 671 | } | |
| 672 | }, | |
| 673 | .error_bundle => { | |
| 674 | result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); | |
| 675 | }, | |
| 676 | .emit_digest => { | |
| 677 | const EmitDigest = std.zig.Server.Message.EmitDigest; | |
| 678 | const ebp_hdr: *align(1) const EmitDigest = @ptrCast(body); | |
| 679 | if (!ebp_hdr.flags.cache_hit) { | |
| 680 | log.info("source changes detected; rebuilt wasm component", .{}); | |
| 681 | } | |
| 682 | const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len]; | |
| 683 | result = .{ | |
| 684 | .root_dir = graph.global_cache_root, | |
| 685 | .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)), | |
| 686 | }; | |
| 687 | }, | |
| 688 | else => {}, // ignore other messages | |
| 689 | } | |
| 690 | } | |
| 691 | ||
| 692 | const stderr_contents = try stderr_task.await(io); | |
| 693 | if (stderr_contents.len > 0) { | |
| 694 | std.debug.print("{s}", .{stderr_contents}); | |
| 695 | } | |
| 696 | ||
| 697 | // Send EOF to stdin. | |
| 698 | child.stdin.?.close(io); | |
| 699 | child.stdin = null; | |
| 700 | ||
| 701 | switch (try child.wait(io)) { | |
| 702 | .exited => |code| { | |
| 703 | if (code != 0) { | |
| 704 | log.err( | |
| 705 | "the following command exited with error code {d}:\n{s}", | |
| 706 | .{ code, try std.zig.allocPrintCmd(arena, argv.items, .{}) }, | |
| 707 | ); | |
| 708 | return error.WasmCompilationFailed; | |
| 709 | } | |
| 710 | }, | |
| 711 | .signal => |sig| { | |
| 712 | log.err( | |
| 713 | "the following command terminated with signal {t}:\n{s}", | |
| 714 | .{ sig, try std.zig.allocPrintCmd(arena, argv.items, .{}) }, | |
| 715 | ); | |
| 716 | return error.WasmCompilationFailed; | |
| 717 | }, | |
| 718 | .stopped => |sig| { | |
| 719 | log.err( | |
| 720 | "the following command stopped unexpectedly with signal {t}:\n{s}", | |
| 721 | .{ sig, try std.zig.allocPrintCmd(arena, argv.items, .{}) }, | |
| 722 | ); | |
| 723 | return error.WasmCompilationFailed; | |
| 724 | }, | |
| 725 | .unknown => { | |
| 726 | log.err( | |
| 727 | "the following command terminated unexpectedly:\n{s}", | |
| 728 | .{try std.zig.allocPrintCmd(arena, argv.items, .{})}, | |
| 729 | ); | |
| 730 | return error.WasmCompilationFailed; | |
| 731 | }, | |
| 732 | } | |
| 733 | ||
| 734 | if (result_error_bundle.errorMessageCount() > 0) { | |
| 735 | try result_error_bundle.renderToStderr(io, .{}, .auto); | |
| 736 | log.err("the following command failed with {d} compilation errors:\n{s}", .{ | |
| 737 | result_error_bundle.errorMessageCount(), | |
| 738 | try std.zig.allocPrintCmd(arena, argv.items, .{}), | |
| 739 | }); | |
| 740 | return error.WasmCompilationFailed; | |
| 741 | } | |
| 742 | ||
| 743 | const base_path = result orelse { | |
| 744 | log.err("child process failed to report result\n{s}", .{ | |
| 745 | try std.zig.allocPrintCmd(arena, argv.items, .{}), | |
| 746 | }); | |
| 747 | return error.WasmCompilationFailed; | |
| 748 | }; | |
| 749 | const target = std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{ | |
| 750 | .arch_os_abi = arch_os_abi, | |
| 751 | .cpu_features = cpu_features, | |
| 752 | }) catch unreachable) catch unreachable; | |
| 753 | const bin_name = try std.zig.binNameAlloc(arena, .{ | |
| 754 | .root_name = root_name, | |
| 755 | .cpu_arch = target.cpu.arch, | |
| 756 | .os_tag = target.os.tag, | |
| 757 | .ofmt = target.ofmt, | |
| 758 | .abi = target.abi, | |
| 759 | .output_mode = .Exe, | |
| 760 | }); | |
| 761 | return base_path.join(arena, bin_name); | |
| 762 | } | |
| 763 | ||
| 764 | fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 { | |
| 765 | var file_reader: Io.File.Reader = .initStreaming(file, io, &.{}); | |
| 766 | return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) { | |
| 767 | error.ReadFailed => return file_reader.err.?, | |
| 768 | else => |e| return e, | |
| 769 | }; | |
| 770 | } | |
| 771 | ||
| 772 | pub fn updateTimeReportCompile(ws: *WebServer, opts: struct { | |
| 773 | compile_step: Configuration.Step.Index, | |
| 774 | ||
| 775 | use_llvm: bool, | |
| 776 | stats: abi.time_report.CompileResult.Stats, | |
| 777 | ns_total: u64, | |
| 778 | ||
| 779 | llvm_pass_timings_len: u32, | |
| 780 | files_len: u32, | |
| 781 | decls_len: u32, | |
| 782 | ||
| 783 | /// The trailing data of `abi.time_report.CompileResult`, except the step name. | |
| 784 | trailing: []const u8, | |
| 785 | }) void { | |
| 786 | const maker = ws.maker; | |
| 787 | const gpa = maker.gpa; | |
| 788 | const io = maker.graph.io; | |
| 789 | const all_steps = maker.step_stack.keys(); | |
| 790 | ||
| 791 | const step_idx: u32 = for (all_steps, 0..) |s, i| { | |
| 792 | if (s == opts.compile_step) break @intCast(i); | |
| 793 | } else unreachable; | |
| 794 | ||
| 795 | const old_buf = old: { | |
| 796 | ws.time_report_mutex.lock(io) catch return; | |
| 797 | defer ws.time_report_mutex.unlock(io); | |
| 798 | const old = ws.time_report_msgs[step_idx]; | |
| 799 | ws.time_report_msgs[step_idx] = &.{}; | |
| 800 | break :old old; | |
| 801 | }; | |
| 802 | const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.CompileResult) + opts.trailing.len) catch @panic("out of memory"); | |
| 803 | ||
| 804 | const out_header: *align(1) abi.time_report.CompileResult = @ptrCast(buf[0..@sizeOf(abi.time_report.CompileResult)]); | |
| 805 | out_header.* = .{ | |
| 806 | .step_idx = step_idx, | |
| 807 | .flags = .{ | |
| 808 | .use_llvm = opts.use_llvm, | |
| 809 | }, | |
| 810 | .stats = opts.stats, | |
| 811 | .ns_total = opts.ns_total, | |
| 812 | .llvm_pass_timings_len = opts.llvm_pass_timings_len, | |
| 813 | .files_len = opts.files_len, | |
| 814 | .decls_len = opts.decls_len, | |
| 815 | }; | |
| 816 | @memcpy(buf[@sizeOf(abi.time_report.CompileResult)..], opts.trailing); | |
| 817 | ||
| 818 | { | |
| 819 | ws.time_report_mutex.lock(io) catch return; | |
| 820 | defer ws.time_report_mutex.unlock(io); | |
| 821 | assert(ws.time_report_msgs[step_idx].len == 0); | |
| 822 | ws.time_report_msgs[step_idx] = buf; | |
| 823 | ws.time_report_update_times[step_idx] = ws.now(); | |
| 824 | } | |
| 825 | ws.notifyUpdate(); | |
| 826 | } | |
| 827 | ||
| 828 | pub fn updateTimeReportGeneric(ws: *WebServer, step_index: Configuration.Step.Index, duration: Io.Duration) void { | |
| 829 | const maker = ws.maker; | |
| 830 | const gpa = maker.gpa; | |
| 831 | const io = maker.graph.io; | |
| 832 | const all_steps = maker.step_stack.keys(); | |
| 833 | ||
| 834 | const step_idx: u32 = for (all_steps, 0..) |s, i| { | |
| 835 | if (s == step_index) break @intCast(i); | |
| 836 | } else unreachable; | |
| 837 | ||
| 838 | const old_buf = old: { | |
| 839 | ws.time_report_mutex.lock(io) catch return; | |
| 840 | defer ws.time_report_mutex.unlock(io); | |
| 841 | const old = ws.time_report_msgs[step_idx]; | |
| 842 | ws.time_report_msgs[step_idx] = &.{}; | |
| 843 | break :old old; | |
| 844 | }; | |
| 845 | const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.GenericResult)) catch @panic("out of memory"); | |
| 846 | const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf); | |
| 847 | out.* = .{ | |
| 848 | .step_idx = step_idx, | |
| 849 | .ns_total = @intCast(duration.toNanoseconds()), | |
| 850 | }; | |
| 851 | { | |
| 852 | ws.time_report_mutex.lock(io) catch return; | |
| 853 | defer ws.time_report_mutex.unlock(io); | |
| 854 | assert(ws.time_report_msgs[step_idx].len == 0); | |
| 855 | ws.time_report_msgs[step_idx] = buf; | |
| 856 | ws.time_report_update_times[step_idx] = ws.now(); | |
| 857 | } | |
| 858 | ws.notifyUpdate(); | |
| 859 | } | |
| 860 | ||
| 861 | pub fn updateTimeReportRunTest( | |
| 862 | ws: *WebServer, | |
| 863 | run_step_index: Configuration.Step.Index, | |
| 864 | tests: *const Step.Run.CachedTestMetadata, | |
| 865 | ns_per_test: []const u64, | |
| 866 | ) void { | |
| 867 | const maker = ws.maker; | |
| 868 | const gpa = maker.gpa; | |
| 869 | const io = maker.graph.io; | |
| 870 | const all_steps = maker.step_stack.keys(); | |
| 871 | ||
| 872 | const step_idx: u32 = for (all_steps, 0..) |s, i| { | |
| 873 | if (s == run_step_index) break @intCast(i); | |
| 874 | } else unreachable; | |
| 875 | ||
| 876 | assert(tests.names.len == ns_per_test.len); | |
| 877 | const tests_len: u32 = @intCast(tests.names.len); | |
| 878 | ||
| 879 | const new_len: u64 = len: { | |
| 880 | var names_len: u64 = 0; | |
| 881 | for (0..tests_len) |i| { | |
| 882 | names_len += tests.testName(@intCast(i)).len + 1; | |
| 883 | } | |
| 884 | break :len @sizeOf(abi.time_report.RunTestResult) + names_len + 8 * tests_len; | |
| 885 | }; | |
| 886 | const old_buf = old: { | |
| 887 | ws.time_report_mutex.lock(io) catch return; | |
| 888 | defer ws.time_report_mutex.unlock(io); | |
| 889 | const old = ws.time_report_msgs[step_idx]; | |
| 890 | ws.time_report_msgs[step_idx] = &.{}; | |
| 891 | break :old old; | |
| 892 | }; | |
| 893 | const buf = gpa.realloc(old_buf, new_len) catch @panic("out of memory"); | |
| 894 | ||
| 895 | const out_header: *align(1) abi.time_report.RunTestResult = @ptrCast(buf[0..@sizeOf(abi.time_report.RunTestResult)]); | |
| 896 | out_header.* = .{ | |
| 897 | .step_idx = step_idx, | |
| 898 | .tests_len = tests_len, | |
| 899 | }; | |
| 900 | var offset: usize = @sizeOf(abi.time_report.RunTestResult); | |
| 901 | const ns_per_test_out: []align(1) u64 = @ptrCast(buf[offset..][0 .. tests_len * 8]); | |
| 902 | @memcpy(ns_per_test_out, ns_per_test); | |
| 903 | offset += tests_len * 8; | |
| 904 | for (0..tests_len) |i| { | |
| 905 | const name = tests.testName(@intCast(i)); | |
| 906 | @memcpy(buf[offset..][0..name.len], name); | |
| 907 | buf[offset..][name.len] = 0; | |
| 908 | offset += name.len + 1; | |
| 909 | } | |
| 910 | assert(offset == buf.len); | |
| 911 | ||
| 912 | { | |
| 913 | ws.time_report_mutex.lock(io) catch return; | |
| 914 | defer ws.time_report_mutex.unlock(io); | |
| 915 | assert(ws.time_report_msgs[step_idx].len == 0); | |
| 916 | ws.time_report_msgs[step_idx] = buf; | |
| 917 | ws.time_report_update_times[step_idx] = ws.now(); | |
| 918 | } | |
| 919 | ws.notifyUpdate(); | |
| 920 | } | |
| 921 | ||
| 922 | const RunnerRequest = union(enum) { | |
| 923 | rebuild, | |
| 924 | }; | |
| 925 | pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest { | |
| 926 | const io = ws.maker.graph.io; | |
| 927 | ws.runner_request_mutex.lock(io) catch return; | |
| 928 | defer ws.runner_request_mutex.unlock(io); | |
| 929 | if (ws.runner_request) |req| { | |
| 930 | ws.runner_request = null; | |
| 931 | ws.runner_request_empty_cond.signal(); | |
| 932 | return req; | |
| 933 | } | |
| 934 | return null; | |
| 935 | } | |
| 936 | pub fn wait(ws: *WebServer) Io.Cancelable!RunnerRequest { | |
| 937 | const io = ws.maker.graph.io; | |
| 938 | try ws.runner_request_mutex.lock(io); | |
| 939 | defer ws.runner_request_mutex.unlock(io); | |
| 940 | while (true) { | |
| 941 | if (ws.runner_request) |req| { | |
| 942 | ws.runner_request = null; | |
| 943 | ws.runner_request_empty_cond.signal(io); | |
| 944 | return req; | |
| 945 | } | |
| 946 | try ws.runner_request_ready_cond.wait(io, &ws.runner_request_mutex); | |
| 947 | } | |
| 948 | } | |
| 949 | ||
| 950 | const cache_control_header: http.Header = .{ | |
| 951 | .name = "Cache-Control", | |
| 952 | .value = "max-age=0, must-revalidate", | |
| 953 | }; |
lib/compiler/aro/aro/Driver.zig+4-3| ... | ... | @@ -1041,9 +1041,10 @@ fn parseTarget(d: *Driver, arch_os_abi: []const u8, opt_cpu_features: ?[]const u |
| 1041 | 1041 | } else if (mem.eql(u8, cpu_name, "baseline")) { |
| 1042 | 1042 | query.cpu_model = .baseline; |
| 1043 | 1043 | } else { |
| 1044 | query.cpu_model = .{ .explicit = arch.parseCpuModel(cpu_name) catch |er| switch (er) { | |
| 1045 | error.UnknownCpuModel => return d.fatal("unknown CPU model: '{s}'", .{cpu_name}), | |
| 1046 | } }; | |
| 1044 | query.cpu_model = .{ | |
| 1045 | .explicit = arch.parseCpuModel(cpu_name) orelse | |
| 1046 | return d.fatal("unknown CPU model: '{s}'", .{cpu_name}), | |
| 1047 | }; | |
| 1047 | 1048 | } |
| 1048 | 1049 | |
| 1049 | 1050 | if (opt_sub_arch) |sub_arch| { |
lib/compiler/build_runner.zig deleted-1857| ... | ... | @@ -1,1857 +0,0 @@ |
| 1 | const runner = @This(); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | const Io = std.Io; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const fmt = std.fmt; | |
| 8 | const mem = std.mem; | |
| 9 | const process = std.process; | |
| 10 | const File = std.Io.File; | |
| 11 | const Step = std.Build.Step; | |
| 12 | const Watch = std.Build.Watch; | |
| 13 | const WebServer = std.Build.WebServer; | |
| 14 | const Allocator = std.mem.Allocator; | |
| 15 | const fatal = std.process.fatal; | |
| 16 | const Writer = std.Io.Writer; | |
| 17 | ||
| 18 | pub const root = @import("@build"); | |
| 19 | pub const dependencies = @import("@dependencies"); | |
| 20 | ||
| 21 | pub const std_options: std.Options = .{ | |
| 22 | .side_channels_mitigations = .none, | |
| 23 | .http_disable_tls = true, | |
| 24 | }; | |
| 25 | ||
| 26 | pub fn main(init: process.Init.Minimal) !void { | |
| 27 | // The build runner is often short-lived, but thanks to `--watch` and `--webui`, that's not | |
| 28 | // always the case. So, we do need a true gpa for some things. | |
| 29 | var safe_gpa_state: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{}); | |
| 30 | defer _ = safe_gpa_state.deinit(); | |
| 31 | const gpa = safe_gpa_state.allocator(); | |
| 32 | ||
| 33 | var threaded: std.Io.Threaded = .init(gpa, .{ | |
| 34 | .environ = init.environ, | |
| 35 | .argv0 = .init(init.args), | |
| 36 | }); | |
| 37 | defer threaded.deinit(); | |
| 38 | const io = threaded.io(); | |
| 39 | ||
| 40 | // ...but we'll back our arena by `std.heap.page_allocator` for efficiency. | |
| 41 | var arena_instance: std.heap.ArenaAllocator = .init(std.heap.page_allocator); | |
| 42 | defer arena_instance.deinit(); | |
| 43 | const arena = arena_instance.allocator(); | |
| 44 | ||
| 45 | const args = try init.args.toSlice(arena); | |
| 46 | ||
| 47 | // skip my own exe name | |
| 48 | var arg_idx: usize = 1; | |
| 49 | ||
| 50 | const zig_exe = nextArg(args, &arg_idx) orelse fatal("missing zig compiler path", .{}); | |
| 51 | const zig_lib_dir = nextArg(args, &arg_idx) orelse fatal("missing zig lib directory path", .{}); | |
| 52 | const build_root = nextArg(args, &arg_idx) orelse fatal("missing build root directory path", .{}); | |
| 53 | const cache_root = nextArg(args, &arg_idx) orelse fatal("missing cache root directory path", .{}); | |
| 54 | const global_cache_root = nextArg(args, &arg_idx) orelse fatal("missing global cache root directory path", .{}); | |
| 55 | ||
| 56 | const cwd: Io.Dir = .cwd(); | |
| 57 | ||
| 58 | const zig_lib_directory: std.Build.Cache.Directory = .{ | |
| 59 | .path = zig_lib_dir, | |
| 60 | .handle = try cwd.openDir(io, zig_lib_dir, .{}), | |
| 61 | }; | |
| 62 | ||
| 63 | const build_root_directory: std.Build.Cache.Directory = .{ | |
| 64 | .path = build_root, | |
| 65 | .handle = try cwd.openDir(io, build_root, .{}), | |
| 66 | }; | |
| 67 | ||
| 68 | const local_cache_directory: std.Build.Cache.Directory = .{ | |
| 69 | .path = cache_root, | |
| 70 | .handle = try cwd.createDirPathOpen(io, cache_root, .{}), | |
| 71 | }; | |
| 72 | ||
| 73 | const global_cache_directory: std.Build.Cache.Directory = .{ | |
| 74 | .path = global_cache_root, | |
| 75 | .handle = try cwd.createDirPathOpen(io, global_cache_root, .{}), | |
| 76 | }; | |
| 77 | ||
| 78 | var graph: std.Build.Graph = .{ | |
| 79 | .io = io, | |
| 80 | .arena = arena, | |
| 81 | .cache = .{ | |
| 82 | .io = io, | |
| 83 | .gpa = gpa, | |
| 84 | .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), | |
| 85 | .cwd = try process.currentPathAlloc(io, arena), | |
| 86 | }, | |
| 87 | .zig_exe = zig_exe, | |
| 88 | .environ_map = try init.environ.createMap(arena), | |
| 89 | .global_cache_root = global_cache_directory, | |
| 90 | .zig_lib_directory = zig_lib_directory, | |
| 91 | .host = .{ | |
| 92 | .query = .{}, | |
| 93 | .result = try std.zig.system.resolveTargetQuery(io, .{}), | |
| 94 | }, | |
| 95 | .time_report = false, | |
| 96 | }; | |
| 97 | ||
| 98 | graph.cache.addPrefix(.{ .path = null, .handle = cwd }); | |
| 99 | graph.cache.addPrefix(build_root_directory); | |
| 100 | graph.cache.addPrefix(local_cache_directory); | |
| 101 | graph.cache.addPrefix(global_cache_directory); | |
| 102 | graph.cache.hash.addBytes(builtin.zig_version_string); | |
| 103 | ||
| 104 | const builder = try std.Build.create( | |
| 105 | &graph, | |
| 106 | build_root_directory, | |
| 107 | local_cache_directory, | |
| 108 | dependencies.root_deps, | |
| 109 | ); | |
| 110 | ||
| 111 | var targets = std.array_list.Managed([]const u8).init(arena); | |
| 112 | var debug_log_scopes = std.array_list.Managed([]const u8).init(arena); | |
| 113 | ||
| 114 | var install_prefix: ?[]const u8 = null; | |
| 115 | var dir_list = std.Build.DirList{}; | |
| 116 | var error_style: ErrorStyle = .verbose; | |
| 117 | var multiline_errors: MultilineErrors = .indent; | |
| 118 | var summary: ?Summary = null; | |
| 119 | var max_rss: u64 = 0; | |
| 120 | var skip_oom_steps = false; | |
| 121 | var test_timeout_ns: ?u64 = null; | |
| 122 | var color: Color = .auto; | |
| 123 | var help_menu = false; | |
| 124 | var steps_menu = false; | |
| 125 | var output_tmp_nonce: ?[16]u8 = null; | |
| 126 | var watch = false; | |
| 127 | var fuzz: ?std.Build.Fuzz.Mode = null; | |
| 128 | var debounce_interval_ms: u16 = 50; | |
| 129 | var webui_listen: ?Io.net.IpAddress = null; | |
| 130 | ||
| 131 | if (std.zig.EnvVar.ZIG_BUILD_ERROR_STYLE.get(&graph.environ_map)) |str| { | |
| 132 | if (std.meta.stringToEnum(ErrorStyle, str)) |style| { | |
| 133 | error_style = style; | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | if (std.zig.EnvVar.ZIG_BUILD_MULTILINE_ERRORS.get(&graph.environ_map)) |str| { | |
| 138 | if (std.meta.stringToEnum(MultilineErrors, str)) |style| { | |
| 139 | multiline_errors = style; | |
| 140 | } | |
| 141 | } | |
| 142 | ||
| 143 | while (nextArg(args, &arg_idx)) |arg| { | |
| 144 | if (mem.startsWith(u8, arg, "-Z")) { | |
| 145 | if (arg.len != 18) fatalWithHint("bad argument: '{s}'", .{arg}); | |
| 146 | output_tmp_nonce = arg[2..18].*; | |
| 147 | } else if (mem.startsWith(u8, arg, "-D")) { | |
| 148 | const option_contents = arg[2..]; | |
| 149 | if (option_contents.len == 0) | |
| 150 | fatalWithHint("expected option name after '-D'", .{}); | |
| 151 | if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| { | |
| 152 | const option_name = option_contents[0..name_end]; | |
| 153 | const option_value = option_contents[name_end + 1 ..]; | |
| 154 | if (try builder.addUserInputOption(option_name, option_value)) | |
| 155 | fatal(" access the help menu with 'zig build -h'", .{}); | |
| 156 | } else { | |
| 157 | if (try builder.addUserInputFlag(option_contents)) | |
| 158 | fatal(" access the help menu with 'zig build -h'", .{}); | |
| 159 | } | |
| 160 | } else if (mem.startsWith(u8, arg, "-")) { | |
| 161 | if (mem.eql(u8, arg, "--verbose")) { | |
| 162 | builder.verbose = true; | |
| 163 | } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { | |
| 164 | help_menu = true; | |
| 165 | } else if (mem.eql(u8, arg, "-p") or mem.eql(u8, arg, "--prefix")) { | |
| 166 | install_prefix = nextArgOrFatal(args, &arg_idx); | |
| 167 | } else if (mem.eql(u8, arg, "-l") or mem.eql(u8, arg, "--list-steps")) { | |
| 168 | steps_menu = true; | |
| 169 | } else if (mem.startsWith(u8, arg, "-fsys=")) { | |
| 170 | const name = arg["-fsys=".len..]; | |
| 171 | graph.system_library_options.put(arena, name, .user_enabled) catch @panic("OOM"); | |
| 172 | } else if (mem.startsWith(u8, arg, "-fno-sys=")) { | |
| 173 | const name = arg["-fno-sys=".len..]; | |
| 174 | graph.system_library_options.put(arena, name, .user_disabled) catch @panic("OOM"); | |
| 175 | } else if (mem.eql(u8, arg, "--release")) { | |
| 176 | builder.release_mode = .any; | |
| 177 | } else if (mem.startsWith(u8, arg, "--release=")) { | |
| 178 | const text = arg["--release=".len..]; | |
| 179 | builder.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, text) orelse { | |
| 180 | fatalWithHint("expected [off|any|fast|safe|small] in '{s}', found '{s}'", .{ | |
| 181 | arg, text, | |
| 182 | }); | |
| 183 | }; | |
| 184 | } else if (mem.eql(u8, arg, "--prefix-lib-dir")) { | |
| 185 | dir_list.lib_dir = nextArgOrFatal(args, &arg_idx); | |
| 186 | } else if (mem.eql(u8, arg, "--prefix-exe-dir")) { | |
| 187 | dir_list.exe_dir = nextArgOrFatal(args, &arg_idx); | |
| 188 | } else if (mem.eql(u8, arg, "--prefix-include-dir")) { | |
| 189 | dir_list.include_dir = nextArgOrFatal(args, &arg_idx); | |
| 190 | } else if (mem.eql(u8, arg, "--sysroot")) { | |
| 191 | builder.sysroot = nextArgOrFatal(args, &arg_idx); | |
| 192 | } else if (mem.eql(u8, arg, "--maxrss")) { | |
| 193 | const max_rss_text = nextArgOrFatal(args, &arg_idx); | |
| 194 | max_rss = std.fmt.parseIntSizeSuffix(max_rss_text, 10) catch |err| { | |
| 195 | std.debug.print("invalid byte size: '{s}': {s}\n", .{ | |
| 196 | max_rss_text, @errorName(err), | |
| 197 | }); | |
| 198 | process.exit(1); | |
| 199 | }; | |
| 200 | } else if (mem.eql(u8, arg, "--skip-oom-steps")) { | |
| 201 | skip_oom_steps = true; | |
| 202 | } else if (mem.eql(u8, arg, "--test-timeout")) { | |
| 203 | const units: []const struct { []const u8, u64 } = &.{ | |
| 204 | .{ "ns", 1 }, | |
| 205 | .{ "nanosecond", 1 }, | |
| 206 | .{ "us", std.time.ns_per_us }, | |
| 207 | .{ "microsecond", std.time.ns_per_us }, | |
| 208 | .{ "ms", std.time.ns_per_ms }, | |
| 209 | .{ "millisecond", std.time.ns_per_ms }, | |
| 210 | .{ "s", std.time.ns_per_s }, | |
| 211 | .{ "second", std.time.ns_per_s }, | |
| 212 | .{ "m", std.time.ns_per_min }, | |
| 213 | .{ "minute", std.time.ns_per_min }, | |
| 214 | .{ "h", std.time.ns_per_hour }, | |
| 215 | .{ "hour", std.time.ns_per_hour }, | |
| 216 | }; | |
| 217 | const timeout_str = nextArgOrFatal(args, &arg_idx); | |
| 218 | const num_end_idx = std.mem.findLastNone(u8, timeout_str, "abcdefghijklmnopqrstuvwxyz") orelse fatal( | |
| 219 | "invalid timeout '{s}': expected unit (ns, us, ms, s, m, h)", | |
| 220 | .{timeout_str}, | |
| 221 | ); | |
| 222 | const num_str = timeout_str[0 .. num_end_idx + 1]; | |
| 223 | const unit_str = timeout_str[num_end_idx + 1 ..]; | |
| 224 | const unit_factor: f64 = for (units) |unit_and_factor| { | |
| 225 | if (std.mem.eql(u8, unit_str, unit_and_factor[0])) { | |
| 226 | break @floatFromInt(unit_and_factor[1]); | |
| 227 | } | |
| 228 | } else fatal( | |
| 229 | "invalid timeout '{s}': invalid unit '{s}' (expected ns, us, ms, s, m, h)", | |
| 230 | .{ timeout_str, unit_str }, | |
| 231 | ); | |
| 232 | const num_parsed = std.fmt.parseFloat(f64, num_str) catch |err| fatal( | |
| 233 | "invalid timeout '{s}': invalid number '{s}' ({t})", | |
| 234 | .{ timeout_str, num_str, err }, | |
| 235 | ); | |
| 236 | test_timeout_ns = std.math.lossyCast(u64, unit_factor * num_parsed); | |
| 237 | } else if (mem.eql(u8, arg, "--search-prefix")) { | |
| 238 | const search_prefix = nextArgOrFatal(args, &arg_idx); | |
| 239 | builder.addSearchPrefix(search_prefix); | |
| 240 | } else if (mem.eql(u8, arg, "--libc")) { | |
| 241 | builder.libc_file = nextArgOrFatal(args, &arg_idx); | |
| 242 | } else if (mem.eql(u8, arg, "--color")) { | |
| 243 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 244 | fatalWithHint("expected [auto|on|off] after '{s}'", .{arg}); | |
| 245 | color = std.meta.stringToEnum(Color, next_arg) orelse { | |
| 246 | fatalWithHint("expected [auto|on|off] after '{s}', found '{s}'", .{ | |
| 247 | arg, next_arg, | |
| 248 | }); | |
| 249 | }; | |
| 250 | } else if (mem.eql(u8, arg, "--error-style")) { | |
| 251 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 252 | fatalWithHint("expected style after '{s}'", .{arg}); | |
| 253 | error_style = std.meta.stringToEnum(ErrorStyle, next_arg) orelse { | |
| 254 | fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg }); | |
| 255 | }; | |
| 256 | } else if (mem.eql(u8, arg, "--multiline-errors")) { | |
| 257 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 258 | fatalWithHint("expected style after '{s}'", .{arg}); | |
| 259 | multiline_errors = std.meta.stringToEnum(MultilineErrors, next_arg) orelse { | |
| 260 | fatalWithHint("expected style after '{s}', found '{s}'", .{ arg, next_arg }); | |
| 261 | }; | |
| 262 | } else if (mem.eql(u8, arg, "--summary")) { | |
| 263 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 264 | fatalWithHint("expected [all|new|failures|line|none] after '{s}'", .{arg}); | |
| 265 | summary = std.meta.stringToEnum(Summary, next_arg) orelse { | |
| 266 | fatalWithHint("expected [all|new|failures|line|none] after '{s}', found '{s}'", .{ | |
| 267 | arg, next_arg, | |
| 268 | }); | |
| 269 | }; | |
| 270 | } else if (mem.eql(u8, arg, "--seed")) { | |
| 271 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 272 | fatalWithHint("expected u32 after '{s}'", .{arg}); | |
| 273 | graph.random_seed = std.fmt.parseUnsigned(u32, next_arg, 0) catch |err| { | |
| 274 | fatal("unable to parse seed '{s}' as unsigned 32-bit integer: {s}\n", .{ | |
| 275 | next_arg, @errorName(err), | |
| 276 | }); | |
| 277 | }; | |
| 278 | } else if (mem.eql(u8, arg, "--build-id")) { | |
| 279 | builder.build_id = .fast; | |
| 280 | } else if (mem.startsWith(u8, arg, "--build-id=")) { | |
| 281 | const style = arg["--build-id=".len..]; | |
| 282 | builder.build_id = std.zig.BuildId.parse(style) catch |err| { | |
| 283 | fatal("unable to parse --build-id style '{s}': {s}", .{ | |
| 284 | style, @errorName(err), | |
| 285 | }); | |
| 286 | }; | |
| 287 | } else if (mem.eql(u8, arg, "--debounce")) { | |
| 288 | const next_arg = nextArg(args, &arg_idx) orelse | |
| 289 | fatalWithHint("expected u16 after '{s}'", .{arg}); | |
| 290 | debounce_interval_ms = std.fmt.parseUnsigned(u16, next_arg, 0) catch |err| { | |
| 291 | fatal("unable to parse debounce interval '{s}' as unsigned 16-bit integer: {t}\n", .{ | |
| 292 | next_arg, err, | |
| 293 | }); | |
| 294 | }; | |
| 295 | } else if (mem.eql(u8, arg, "--webui")) { | |
| 296 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; | |
| 297 | } else if (mem.startsWith(u8, arg, "--webui=")) { | |
| 298 | const addr_str = arg["--webui=".len..]; | |
| 299 | if (std.mem.eql(u8, addr_str, "-")) fatal("web interface cannot listen on stdio", .{}); | |
| 300 | webui_listen = Io.net.IpAddress.parseLiteral(addr_str) catch |err| { | |
| 301 | fatal("invalid web UI address '{s}': {s}", .{ addr_str, @errorName(err) }); | |
| 302 | }; | |
| 303 | } else if (mem.eql(u8, arg, "--debug-log")) { | |
| 304 | const next_arg = nextArgOrFatal(args, &arg_idx); | |
| 305 | try debug_log_scopes.append(next_arg); | |
| 306 | } else if (mem.eql(u8, arg, "--debug-pkg-config")) { | |
| 307 | builder.debug_pkg_config = true; | |
| 308 | } else if (mem.eql(u8, arg, "--debug-rt")) { | |
| 309 | graph.debug_compiler_runtime_libs = .Debug; | |
| 310 | } else if (mem.cutPrefix(u8, arg, "--debug-rt=")) |rest| { | |
| 311 | graph.debug_compiler_runtime_libs = | |
| 312 | std.meta.stringToEnum(std.builtin.OptimizeMode, rest) orelse | |
| 313 | fatal("unrecognized optimization mode: '{s}'", .{rest}); | |
| 314 | } else if (mem.eql(u8, arg, "--debug-compile-errors")) { | |
| 315 | builder.debug_compile_errors = true; | |
| 316 | } else if (mem.eql(u8, arg, "--debug-incremental")) { | |
| 317 | builder.debug_incremental = true; | |
| 318 | } else if (mem.eql(u8, arg, "--system")) { | |
| 319 | // The usage text shows another argument after this parameter | |
| 320 | // but it is handled by the parent process. The build runner | |
| 321 | // only sees this flag. | |
| 322 | graph.system_package_mode = true; | |
| 323 | } else if (mem.eql(u8, arg, "--libc-runtimes") or mem.eql(u8, arg, "--glibc-runtimes")) { | |
| 324 | // --glibc-runtimes was the old name of the flag; kept for compatibility for now. | |
| 325 | builder.libc_runtimes_dir = nextArgOrFatal(args, &arg_idx); | |
| 326 | } else if (mem.eql(u8, arg, "--verbose-link")) { | |
| 327 | builder.verbose_link = true; | |
| 328 | } else if (mem.eql(u8, arg, "--verbose-air")) { | |
| 329 | builder.verbose_air = true; | |
| 330 | } else if (mem.eql(u8, arg, "--verbose-llvm-ir")) { | |
| 331 | builder.verbose_llvm_ir = "-"; | |
| 332 | } else if (mem.startsWith(u8, arg, "--verbose-llvm-ir=")) { | |
| 333 | builder.verbose_llvm_ir = arg["--verbose-llvm-ir=".len..]; | |
| 334 | } else if (mem.startsWith(u8, arg, "--verbose-llvm-bc=")) { | |
| 335 | builder.verbose_llvm_bc = arg["--verbose-llvm-bc=".len..]; | |
| 336 | } else if (mem.eql(u8, arg, "--verbose-cc")) { | |
| 337 | builder.verbose_cc = true; | |
| 338 | } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) { | |
| 339 | builder.verbose_llvm_cpu_features = true; | |
| 340 | } else if (mem.eql(u8, arg, "--watch")) { | |
| 341 | watch = true; | |
| 342 | } else if (mem.eql(u8, arg, "--time-report")) { | |
| 343 | graph.time_report = true; | |
| 344 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; | |
| 345 | } else if (mem.eql(u8, arg, "--fuzz")) { | |
| 346 | fuzz = .{ .forever = undefined }; | |
| 347 | if (webui_listen == null) webui_listen = .{ .ip6 = .loopback(0) }; | |
| 348 | } else if (mem.startsWith(u8, arg, "--fuzz=")) { | |
| 349 | const value = arg["--fuzz=".len..]; | |
| 350 | if (value.len == 0) fatal("missing argument to --fuzz", .{}); | |
| 351 | ||
| 352 | const unit: u8 = value[value.len - 1]; | |
| 353 | const digits = switch (unit) { | |
| 354 | '0'...'9' => value, | |
| 355 | 'K', 'M', 'G' => value[0 .. value.len - 1], | |
| 356 | else => fatal( | |
| 357 | "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]", | |
| 358 | .{}, | |
| 359 | ), | |
| 360 | }; | |
| 361 | ||
| 362 | const amount = std.fmt.parseInt(u64, digits, 10) catch { | |
| 363 | fatal( | |
| 364 | "invalid argument to --fuzz, expected a positive number optionally suffixed by one of: [KMG]", | |
| 365 | .{}, | |
| 366 | ); | |
| 367 | }; | |
| 368 | ||
| 369 | const normalized_amount = std.math.mul(u64, amount, switch (unit) { | |
| 370 | else => unreachable, | |
| 371 | '0'...'9' => 1, | |
| 372 | 'K' => 1000, | |
| 373 | 'M' => 1_000_000, | |
| 374 | 'G' => 1_000_000_000, | |
| 375 | }) catch fatal("fuzzing limit amount overflows u64", .{}); | |
| 376 | ||
| 377 | fuzz = .{ | |
| 378 | .limit = .{ | |
| 379 | .amount = normalized_amount, | |
| 380 | }, | |
| 381 | }; | |
| 382 | } else if (mem.eql(u8, arg, "-fincremental")) { | |
| 383 | graph.incremental = true; | |
| 384 | } else if (mem.eql(u8, arg, "-fno-incremental")) { | |
| 385 | graph.incremental = false; | |
| 386 | } else if (mem.eql(u8, arg, "-fwine")) { | |
| 387 | builder.enable_wine = true; | |
| 388 | } else if (mem.eql(u8, arg, "-fno-wine")) { | |
| 389 | builder.enable_wine = false; | |
| 390 | } else if (mem.eql(u8, arg, "-fqemu")) { | |
| 391 | builder.enable_qemu = true; | |
| 392 | } else if (mem.eql(u8, arg, "-fno-qemu")) { | |
| 393 | builder.enable_qemu = false; | |
| 394 | } else if (mem.eql(u8, arg, "-fwasmtime")) { | |
| 395 | builder.enable_wasmtime = true; | |
| 396 | } else if (mem.eql(u8, arg, "-fno-wasmtime")) { | |
| 397 | builder.enable_wasmtime = false; | |
| 398 | } else if (mem.eql(u8, arg, "-frosetta")) { | |
| 399 | builder.enable_rosetta = true; | |
| 400 | } else if (mem.eql(u8, arg, "-fno-rosetta")) { | |
| 401 | builder.enable_rosetta = false; | |
| 402 | } else if (mem.eql(u8, arg, "-fdarling")) { | |
| 403 | builder.enable_darling = true; | |
| 404 | } else if (mem.eql(u8, arg, "-fno-darling")) { | |
| 405 | builder.enable_darling = false; | |
| 406 | } else if (mem.eql(u8, arg, "-fallow-so-scripts")) { | |
| 407 | graph.allow_so_scripts = true; | |
| 408 | } else if (mem.eql(u8, arg, "-fno-allow-so-scripts")) { | |
| 409 | graph.allow_so_scripts = false; | |
| 410 | } else if (mem.eql(u8, arg, "-freference-trace")) { | |
| 411 | builder.reference_trace = 256; | |
| 412 | } else if (mem.startsWith(u8, arg, "-freference-trace=")) { | |
| 413 | const num = arg["-freference-trace=".len..]; | |
| 414 | builder.reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| { | |
| 415 | std.debug.print("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) }); | |
| 416 | process.exit(1); | |
| 417 | }; | |
| 418 | } else if (mem.eql(u8, arg, "-fno-reference-trace")) { | |
| 419 | builder.reference_trace = null; | |
| 420 | } else if (mem.cutPrefix(u8, arg, "-j")) |text| { | |
| 421 | const n = std.fmt.parseUnsigned(u32, text, 10) catch |err| | |
| 422 | fatal("unable to parse jobs count '{s}': {t}", .{ text, err }); | |
| 423 | if (n < 1) fatal("number of jobs must be at least 1", .{}); | |
| 424 | threaded.setAsyncLimit(.limited(n)); | |
| 425 | graph.max_jobs = n; | |
| 426 | } else if (mem.eql(u8, arg, "--")) { | |
| 427 | builder.args = argsRest(args, arg_idx); | |
| 428 | break; | |
| 429 | } else { | |
| 430 | fatalWithHint("unrecognized argument: '{s}'", .{arg}); | |
| 431 | } | |
| 432 | } else { | |
| 433 | try targets.append(arg); | |
| 434 | } | |
| 435 | } | |
| 436 | ||
| 437 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map); | |
| 438 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map); | |
| 439 | ||
| 440 | graph.stderr_mode = switch (color) { | |
| 441 | .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), | |
| 442 | .on => .escape_codes, | |
| 443 | .off => .no_color, | |
| 444 | }; | |
| 445 | ||
| 446 | if (webui_listen != null) { | |
| 447 | if (watch) fatal("using '--webui' and '--watch' together is not yet supported; consider omitting '--watch' in favour of the web UI \"Rebuild\" button", .{}); | |
| 448 | if (builtin.single_threaded) fatal("'--webui' is not yet supported on single-threaded hosts", .{}); | |
| 449 | } | |
| 450 | ||
| 451 | const main_progress_node = std.Progress.start(io, .{ | |
| 452 | .disable_printing = (color == .off), | |
| 453 | }); | |
| 454 | defer main_progress_node.end(); | |
| 455 | ||
| 456 | builder.debug_log_scopes = debug_log_scopes.items; | |
| 457 | builder.resolveInstallPrefix(install_prefix, dir_list); | |
| 458 | { | |
| 459 | var prog_node = main_progress_node.start("Configure", 0); | |
| 460 | defer prog_node.end(); | |
| 461 | try builder.runBuild(root); | |
| 462 | createModuleDependencies(builder) catch @panic("OOM"); | |
| 463 | } | |
| 464 | ||
| 465 | if (graph.needed_lazy_dependencies.entries.len != 0) { | |
| 466 | var buffer: std.ArrayList(u8) = .empty; | |
| 467 | for (graph.needed_lazy_dependencies.keys()) |k| { | |
| 468 | try buffer.appendSlice(arena, k); | |
| 469 | try buffer.append(arena, '\n'); | |
| 470 | } | |
| 471 | const s = std.fs.path.sep_str; | |
| 472 | const tmp_sub_path = "tmp" ++ s ++ (output_tmp_nonce orelse fatal("missing -Z arg", .{})); | |
| 473 | local_cache_directory.handle.writeFile(io, .{ | |
| 474 | .sub_path = tmp_sub_path, | |
| 475 | .data = buffer.items, | |
| 476 | .flags = .{ .exclusive = true }, | |
| 477 | }) catch |err| { | |
| 478 | fatal("unable to write configuration results to '{f}{s}': {s}", .{ | |
| 479 | local_cache_directory, tmp_sub_path, @errorName(err), | |
| 480 | }); | |
| 481 | }; | |
| 482 | process.exit(3); // Indicate configure phase failed with meaningful stdout. | |
| 483 | } | |
| 484 | ||
| 485 | if (builder.validateUserInputDidItFail()) { | |
| 486 | fatal(" access the help menu with 'zig build -h'", .{}); | |
| 487 | } | |
| 488 | ||
| 489 | validateSystemLibraryOptions(builder); | |
| 490 | ||
| 491 | if (help_menu) { | |
| 492 | var w = initStdoutWriter(io); | |
| 493 | printUsage(builder, w) catch return stdout_writer_allocation.err.?; | |
| 494 | w.flush() catch return stdout_writer_allocation.err.?; | |
| 495 | return; | |
| 496 | } | |
| 497 | ||
| 498 | if (steps_menu) { | |
| 499 | var w = initStdoutWriter(io); | |
| 500 | printSteps(builder, w) catch return stdout_writer_allocation.err.?; | |
| 501 | w.flush() catch return stdout_writer_allocation.err.?; | |
| 502 | return; | |
| 503 | } | |
| 504 | ||
| 505 | var run: Run = .{ | |
| 506 | .gpa = gpa, | |
| 507 | ||
| 508 | .available_rss = max_rss, | |
| 509 | .max_rss_is_default = false, | |
| 510 | .max_rss_mutex = .init, | |
| 511 | .skip_oom_steps = skip_oom_steps, | |
| 512 | .unit_test_timeout_ns = test_timeout_ns, | |
| 513 | ||
| 514 | .watch = watch, | |
| 515 | .web_server = undefined, // set after `prepare` | |
| 516 | .memory_blocked_steps = .empty, | |
| 517 | .step_stack = .empty, | |
| 518 | ||
| 519 | .error_style = error_style, | |
| 520 | .multiline_errors = multiline_errors, | |
| 521 | .summary = summary orelse if (watch or webui_listen != null) .line else .failures, | |
| 522 | }; | |
| 523 | defer { | |
| 524 | run.memory_blocked_steps.deinit(gpa); | |
| 525 | run.step_stack.deinit(gpa); | |
| 526 | } | |
| 527 | ||
| 528 | if (run.available_rss == 0) { | |
| 529 | run.available_rss = process.totalSystemMemory() catch std.math.maxInt(u64); | |
| 530 | run.max_rss_is_default = true; | |
| 531 | } | |
| 532 | ||
| 533 | prepare(arena, builder, targets.items, &run, graph.random_seed) catch |err| switch (err) { | |
| 534 | error.DependencyLoopDetected, error.InsufficientMemory => { | |
| 535 | // Perhaps in the future there could be an Advanced Options flag | |
| 536 | // such as --debug-build-runner-leaks which would make this code | |
| 537 | // return instead of calling exit. | |
| 538 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; | |
| 539 | process.exit(1); | |
| 540 | }, | |
| 541 | else => |e| return e, | |
| 542 | }; | |
| 543 | ||
| 544 | var w: Watch = w: { | |
| 545 | if (!watch) break :w undefined; | |
| 546 | if (!Watch.have_impl) fatal("--watch not yet implemented for {t}", .{builtin.os.tag}); | |
| 547 | break :w try .init(graph.cache.cwd); | |
| 548 | }; | |
| 549 | ||
| 550 | const now = Io.Clock.Timestamp.now(io, .awake); | |
| 551 | ||
| 552 | run.web_server = if (webui_listen) |listen_address| ws: { | |
| 553 | if (builtin.single_threaded) unreachable; // `fatal` above | |
| 554 | break :ws .init(.{ | |
| 555 | .gpa = gpa, | |
| 556 | .graph = &graph, | |
| 557 | .all_steps = run.step_stack.keys(), | |
| 558 | .root_prog_node = main_progress_node, | |
| 559 | .watch = watch, | |
| 560 | .listen_address = listen_address, | |
| 561 | .base_timestamp = now, | |
| 562 | }); | |
| 563 | } else null; | |
| 564 | ||
| 565 | if (run.web_server) |*ws| { | |
| 566 | ws.start() catch |err| fatal("failed to start web server: {t}", .{err}); | |
| 567 | } | |
| 568 | ||
| 569 | rebuild: while (true) : (if (run.error_style.clearOnUpdate()) { | |
| 570 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 571 | defer io.unlockStderr(); | |
| 572 | try stderr.file_writer.interface.writeAll("\x1B[2J\x1B[3J\x1B[H"); | |
| 573 | }) { | |
| 574 | if (run.web_server) |*ws| ws.startBuild(); | |
| 575 | ||
| 576 | try runStepNames( | |
| 577 | builder, | |
| 578 | targets.items, | |
| 579 | main_progress_node, | |
| 580 | &run, | |
| 581 | fuzz, | |
| 582 | ); | |
| 583 | ||
| 584 | if (run.web_server) |*web_server| { | |
| 585 | if (fuzz) |mode| if (mode != .forever) fatal( | |
| 586 | "error: limited fuzzing is not implemented yet for --webui", | |
| 587 | .{}, | |
| 588 | ); | |
| 589 | ||
| 590 | web_server.finishBuild(.{ .fuzz = fuzz != null }); | |
| 591 | } | |
| 592 | ||
| 593 | if (run.web_server) |*ws| { | |
| 594 | assert(!watch); // fatal error after CLI parsing | |
| 595 | while (true) switch (try ws.wait()) { | |
| 596 | .rebuild => { | |
| 597 | for (run.step_stack.keys()) |step| { | |
| 598 | step.state = .precheck_done; | |
| 599 | step.pending_deps = @intCast(step.dependencies.items.len); | |
| 600 | step.reset(gpa); | |
| 601 | } | |
| 602 | continue :rebuild; | |
| 603 | }, | |
| 604 | }; | |
| 605 | } | |
| 606 | ||
| 607 | // Comptime-known guard to prevent including the logic below when `!Watch.have_impl`. | |
| 608 | if (!Watch.have_impl) unreachable; | |
| 609 | ||
| 610 | try w.update(gpa, run.step_stack.keys()); | |
| 611 | ||
| 612 | // Wait until a file system notification arrives. Read all such events | |
| 613 | // until the buffer is empty. Then wait for a debounce interval, resetting | |
| 614 | // if any more events come in. After the debounce interval has passed, | |
| 615 | // trigger a rebuild on all steps with modified inputs, as well as their | |
| 616 | // recursive dependants. | |
| 617 | var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined; | |
| 618 | const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{ | |
| 619 | w.dir_count, countSubProcesses(run.step_stack.keys()), | |
| 620 | }) catch &caption_buf; | |
| 621 | var debouncing_node = main_progress_node.start(caption, 0); | |
| 622 | var in_debounce = false; | |
| 623 | while (true) switch (try w.wait(gpa, io, if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) { | |
| 624 | .timeout => { | |
| 625 | assert(in_debounce); | |
| 626 | debouncing_node.end(); | |
| 627 | markFailedStepsDirty(gpa, run.step_stack.keys()); | |
| 628 | continue :rebuild; | |
| 629 | }, | |
| 630 | .dirty => if (!in_debounce) { | |
| 631 | in_debounce = true; | |
| 632 | debouncing_node.end(); | |
| 633 | debouncing_node = main_progress_node.start("Debouncing (Change Detected)", 0); | |
| 634 | }, | |
| 635 | .clean => {}, | |
| 636 | }; | |
| 637 | } | |
| 638 | } | |
| 639 | ||
| 640 | fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void { | |
| 641 | for (all_steps) |step| switch (step.state) { | |
| 642 | .dependency_failure, .failure, .skipped => _ = step.invalidateResult(gpa), | |
| 643 | else => continue, | |
| 644 | }; | |
| 645 | // Now that all dirty steps have been found, the remaining steps that | |
| 646 | // succeeded from last run shall be marked "cached". | |
| 647 | for (all_steps) |step| switch (step.state) { | |
| 648 | .success => step.result_cached = true, | |
| 649 | else => continue, | |
| 650 | }; | |
| 651 | } | |
| 652 | ||
| 653 | fn countSubProcesses(all_steps: []const *Step) usize { | |
| 654 | var count: usize = 0; | |
| 655 | for (all_steps) |s| { | |
| 656 | count += @intFromBool(s.getZigProcess() != null); | |
| 657 | } | |
| 658 | return count; | |
| 659 | } | |
| 660 | ||
| 661 | const Run = struct { | |
| 662 | gpa: Allocator, | |
| 663 | ||
| 664 | available_rss: usize, | |
| 665 | max_rss_is_default: bool, | |
| 666 | max_rss_mutex: Io.Mutex, | |
| 667 | skip_oom_steps: bool, | |
| 668 | unit_test_timeout_ns: ?u64, | |
| 669 | watch: bool, | |
| 670 | web_server: if (!builtin.single_threaded) ?WebServer else ?noreturn, | |
| 671 | /// Allocated into `gpa`. | |
| 672 | memory_blocked_steps: std.ArrayList(*Step), | |
| 673 | /// Allocated into `gpa`. | |
| 674 | step_stack: std.AutoArrayHashMapUnmanaged(*Step, void), | |
| 675 | ||
| 676 | error_style: ErrorStyle, | |
| 677 | multiline_errors: MultilineErrors, | |
| 678 | summary: Summary, | |
| 679 | }; | |
| 680 | ||
| 681 | fn prepare( | |
| 682 | arena: Allocator, | |
| 683 | b: *std.Build, | |
| 684 | step_names: []const []const u8, | |
| 685 | run: *Run, | |
| 686 | seed: u32, | |
| 687 | ) !void { | |
| 688 | const gpa = run.gpa; | |
| 689 | const step_stack = &run.step_stack; | |
| 690 | ||
| 691 | if (step_names.len == 0) { | |
| 692 | try step_stack.put(gpa, b.default_step, {}); | |
| 693 | } else { | |
| 694 | try step_stack.ensureUnusedCapacity(gpa, step_names.len); | |
| 695 | for (0..step_names.len) |i| { | |
| 696 | const step_name = step_names[step_names.len - i - 1]; | |
| 697 | const s = b.top_level_steps.get(step_name) orelse { | |
| 698 | std.log.info("access the help menu with \"zig build -h\"", .{}); | |
| 699 | fatal("no step named '{s}'", .{step_name}); | |
| 700 | }; | |
| 701 | step_stack.putAssumeCapacity(&s.step, {}); | |
| 702 | } | |
| 703 | } | |
| 704 | ||
| 705 | const starting_steps = try arena.dupe(*Step, step_stack.keys()); | |
| 706 | ||
| 707 | var rng = std.Random.DefaultPrng.init(seed); | |
| 708 | const rand = rng.random(); | |
| 709 | rand.shuffle(*Step, starting_steps); | |
| 710 | ||
| 711 | for (starting_steps) |s| { | |
| 712 | try constructGraphAndCheckForDependencyLoop(gpa, b, s, &run.step_stack, rand); | |
| 713 | } | |
| 714 | ||
| 715 | { | |
| 716 | // Check that we have enough memory to complete the build. | |
| 717 | var any_problems = false; | |
| 718 | var max_needed: usize = 0; | |
| 719 | for (step_stack.keys()) |s| { | |
| 720 | if (s.max_rss == 0) continue; | |
| 721 | max_needed = @max(max_needed, s.max_rss); | |
| 722 | if (s.max_rss > run.available_rss) { | |
| 723 | if (run.skip_oom_steps) { | |
| 724 | s.state = .skipped_oom; | |
| 725 | for (s.dependants.items) |dependant| { | |
| 726 | dependant.pending_deps -= 1; | |
| 727 | } | |
| 728 | } else { | |
| 729 | std.log.err("{s}{s}: this step declares an upper bound of {d} bytes of memory, exceeding the available {d} bytes of memory", .{ | |
| 730 | s.owner.dep_prefix, s.name, s.max_rss, run.available_rss, | |
| 731 | }); | |
| 732 | any_problems = true; | |
| 733 | } | |
| 734 | } | |
| 735 | } | |
| 736 | if (any_problems) { | |
| 737 | if (run.max_rss_is_default) { | |
| 738 | std.log.info("use --maxrss {d} to proceed, risking system memory exhaustion", .{ | |
| 739 | max_needed, | |
| 740 | }); | |
| 741 | } | |
| 742 | return error.InsufficientMemory; | |
| 743 | } | |
| 744 | } | |
| 745 | } | |
| 746 | ||
| 747 | fn runStepNames( | |
| 748 | b: *std.Build, | |
| 749 | step_names: []const []const u8, | |
| 750 | parent_prog_node: std.Progress.Node, | |
| 751 | run: *Run, | |
| 752 | fuzz: ?std.Build.Fuzz.Mode, | |
| 753 | ) !void { | |
| 754 | const gpa = run.gpa; | |
| 755 | const graph = b.graph; | |
| 756 | const io = graph.io; | |
| 757 | const step_stack = &run.step_stack; | |
| 758 | ||
| 759 | { | |
| 760 | // Collect the initial set of tasks (those with no outstanding dependencies) into a buffer, | |
| 761 | // then spawn them. The buffer is so that we don't race with `makeStep` and end up thinking | |
| 762 | // a step is initial when it actually became ready due to an earlier initial step. | |
| 763 | var initial_set: std.ArrayList(*Step) = .empty; | |
| 764 | defer initial_set.deinit(gpa); | |
| 765 | try initial_set.ensureUnusedCapacity(gpa, step_stack.count()); | |
| 766 | for (step_stack.keys()) |s| { | |
| 767 | if (s.state == .precheck_done and s.pending_deps == 0) { | |
| 768 | initial_set.appendAssumeCapacity(s); | |
| 769 | } | |
| 770 | } | |
| 771 | ||
| 772 | const step_prog = parent_prog_node.start("steps", step_stack.count()); | |
| 773 | defer step_prog.end(); | |
| 774 | ||
| 775 | var group: Io.Group = .init; | |
| 776 | defer group.cancel(io); | |
| 777 | // Start working on all of the initial steps... | |
| 778 | for (initial_set.items) |s| try stepReady(&group, b, s, step_prog, run); | |
| 779 | // ...and `makeStep` will trigger every other step when their last dependency finishes. | |
| 780 | try group.await(io); | |
| 781 | } | |
| 782 | ||
| 783 | assert(run.memory_blocked_steps.items.len == 0); | |
| 784 | ||
| 785 | var test_pass_count: usize = 0; | |
| 786 | var test_skip_count: usize = 0; | |
| 787 | var test_fail_count: usize = 0; | |
| 788 | var test_crash_count: usize = 0; | |
| 789 | var test_timeout_count: usize = 0; | |
| 790 | ||
| 791 | var test_count: usize = 0; | |
| 792 | ||
| 793 | var success_count: usize = 0; | |
| 794 | var skipped_count: usize = 0; | |
| 795 | var failure_count: usize = 0; | |
| 796 | var pending_count: usize = 0; | |
| 797 | var total_compile_errors: usize = 0; | |
| 798 | ||
| 799 | var cleanup_task = io.async(cleanTmpFiles, .{ io, step_stack.keys() }); | |
| 800 | defer cleanup_task.await(io); | |
| 801 | ||
| 802 | for (step_stack.keys()) |s| { | |
| 803 | test_pass_count += s.test_results.passCount(); | |
| 804 | test_skip_count += s.test_results.skip_count; | |
| 805 | test_fail_count += s.test_results.fail_count; | |
| 806 | test_crash_count += s.test_results.crash_count; | |
| 807 | test_timeout_count += s.test_results.timeout_count; | |
| 808 | ||
| 809 | test_count += s.test_results.test_count; | |
| 810 | ||
| 811 | switch (s.state) { | |
| 812 | .precheck_unstarted => unreachable, | |
| 813 | .precheck_started => unreachable, | |
| 814 | .precheck_done => unreachable, | |
| 815 | .dependency_failure => pending_count += 1, | |
| 816 | .success => success_count += 1, | |
| 817 | .skipped, .skipped_oom => skipped_count += 1, | |
| 818 | .failure => { | |
| 819 | failure_count += 1; | |
| 820 | const compile_errors_len = s.result_error_bundle.errorMessageCount(); | |
| 821 | if (compile_errors_len > 0) { | |
| 822 | total_compile_errors += compile_errors_len; | |
| 823 | } | |
| 824 | }, | |
| 825 | } | |
| 826 | } | |
| 827 | ||
| 828 | if (fuzz) |mode| blk: { | |
| 829 | switch (builtin.os.tag) { | |
| 830 | // Current implementation depends on two things that need to be ported to Windows: | |
| 831 | // * Memory-mapping to share data between the fuzzer and build runner. | |
| 832 | // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving | |
| 833 | // many addresses to source locations). | |
| 834 | .windows => fatal("--fuzz not yet implemented for {t}", .{builtin.os.tag}), | |
| 835 | else => {}, | |
| 836 | } | |
| 837 | if (@bitSizeOf(usize) != 64) { | |
| 838 | // Current implementation depends on posix.mmap()'s second parameter, `length: usize`, | |
| 839 | // being compatible with file system's u64 return value. This is not the case | |
| 840 | // on 32-bit platforms. | |
| 841 | // Affects or affected by issues #5185, #22523, and #22464. | |
| 842 | fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); | |
| 843 | } | |
| 844 | ||
| 845 | switch (mode) { | |
| 846 | .forever => break :blk, | |
| 847 | .limit => {}, | |
| 848 | } | |
| 849 | ||
| 850 | assert(mode == .limit); | |
| 851 | var f = std.Build.Fuzz.init( | |
| 852 | gpa, | |
| 853 | io, | |
| 854 | step_stack.keys(), | |
| 855 | parent_prog_node, | |
| 856 | mode, | |
| 857 | ) catch |err| fatal("failed to start fuzzer: {t}", .{err}); | |
| 858 | defer f.deinit(); | |
| 859 | ||
| 860 | f.start(); | |
| 861 | try f.waitAndPrintReport(); | |
| 862 | } | |
| 863 | ||
| 864 | // Every test has a state | |
| 865 | assert(test_pass_count + test_skip_count + test_fail_count + test_crash_count + test_timeout_count == test_count); | |
| 866 | ||
| 867 | if (failure_count == 0) { | |
| 868 | std.Progress.setStatus(.success); | |
| 869 | } else { | |
| 870 | std.Progress.setStatus(.failure); | |
| 871 | } | |
| 872 | ||
| 873 | summary: { | |
| 874 | switch (run.summary) { | |
| 875 | .all, .new, .line => {}, | |
| 876 | .failures => if (failure_count == 0) break :summary, | |
| 877 | .none => break :summary, | |
| 878 | } | |
| 879 | ||
| 880 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 881 | defer io.unlockStderr(); | |
| 882 | const t = stderr.terminal(); | |
| 883 | const w = &stderr.file_writer.interface; | |
| 884 | ||
| 885 | const total_count = success_count + failure_count + pending_count + skipped_count; | |
| 886 | t.setColor(.cyan) catch {}; | |
| 887 | t.setColor(.bold) catch {}; | |
| 888 | w.writeAll("Build Summary: ") catch {}; | |
| 889 | t.setColor(.reset) catch {}; | |
| 890 | w.print("{d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; | |
| 891 | { | |
| 892 | t.setColor(.dim) catch {}; | |
| 893 | var first = true; | |
| 894 | if (skipped_count > 0) { | |
| 895 | w.print("{s}{d} skipped", .{ if (first) " (" else ", ", skipped_count }) catch {}; | |
| 896 | first = false; | |
| 897 | } | |
| 898 | if (failure_count > 0) { | |
| 899 | w.print("{s}{d} failed", .{ if (first) " (" else ", ", failure_count }) catch {}; | |
| 900 | first = false; | |
| 901 | } | |
| 902 | if (!first) w.writeByte(')') catch {}; | |
| 903 | t.setColor(.reset) catch {}; | |
| 904 | } | |
| 905 | ||
| 906 | if (test_count > 0) { | |
| 907 | w.print("; {d}/{d} tests passed", .{ test_pass_count, test_count }) catch {}; | |
| 908 | t.setColor(.dim) catch {}; | |
| 909 | var first = true; | |
| 910 | if (test_skip_count > 0) { | |
| 911 | w.print("{s}{d} skipped", .{ if (first) " (" else ", ", test_skip_count }) catch {}; | |
| 912 | first = false; | |
| 913 | } | |
| 914 | if (test_fail_count > 0) { | |
| 915 | w.print("{s}{d} failed", .{ if (first) " (" else ", ", test_fail_count }) catch {}; | |
| 916 | first = false; | |
| 917 | } | |
| 918 | if (test_crash_count > 0) { | |
| 919 | w.print("{s}{d} crashed", .{ if (first) " (" else ", ", test_crash_count }) catch {}; | |
| 920 | first = false; | |
| 921 | } | |
| 922 | if (test_timeout_count > 0) { | |
| 923 | w.print("{s}{d} timed out", .{ if (first) " (" else ", ", test_timeout_count }) catch {}; | |
| 924 | first = false; | |
| 925 | } | |
| 926 | if (!first) w.writeByte(')') catch {}; | |
| 927 | t.setColor(.reset) catch {}; | |
| 928 | } | |
| 929 | ||
| 930 | w.writeAll("\n") catch {}; | |
| 931 | ||
| 932 | if (run.summary == .line) break :summary; | |
| 933 | ||
| 934 | // Print a fancy tree with build results. | |
| 935 | var step_stack_copy = try step_stack.clone(gpa); | |
| 936 | defer step_stack_copy.deinit(gpa); | |
| 937 | ||
| 938 | var print_node: PrintNode = .{ .parent = null }; | |
| 939 | if (step_names.len == 0) { | |
| 940 | print_node.last = true; | |
| 941 | printTreeStep(b, b.default_step, run, t, &print_node, &step_stack_copy) catch {}; | |
| 942 | } else { | |
| 943 | const last_index = if (run.summary == .all) b.top_level_steps.count() else blk: { | |
| 944 | var i: usize = step_names.len; | |
| 945 | while (i > 0) { | |
| 946 | i -= 1; | |
| 947 | const step = b.top_level_steps.get(step_names[i]).?.step; | |
| 948 | const found = switch (run.summary) { | |
| 949 | .all, .line, .none => unreachable, | |
| 950 | .failures => step.state != .success, | |
| 951 | .new => !step.result_cached, | |
| 952 | }; | |
| 953 | if (found) break :blk i; | |
| 954 | } | |
| 955 | break :blk b.top_level_steps.count(); | |
| 956 | }; | |
| 957 | for (step_names, 0..) |step_name, i| { | |
| 958 | const tls = b.top_level_steps.get(step_name).?; | |
| 959 | print_node.last = i + 1 == last_index; | |
| 960 | printTreeStep(b, &tls.step, run, t, &print_node, &step_stack_copy) catch {}; | |
| 961 | } | |
| 962 | } | |
| 963 | w.writeByte('\n') catch {}; | |
| 964 | } | |
| 965 | ||
| 966 | if (run.watch or run.web_server != null) return; | |
| 967 | ||
| 968 | // Perhaps in the future there could be an Advanced Options flag such as | |
| 969 | // --debug-build-runner-leaks which would make this code return instead of | |
| 970 | // calling exit. | |
| 971 | ||
| 972 | const code: u8 = code: { | |
| 973 | if (failure_count == 0) break :code 0; // success | |
| 974 | if (run.error_style.verboseContext()) break :code 1; // failure; print build command | |
| 975 | break :code 2; // failure; do not print build command | |
| 976 | }; | |
| 977 | _ = io.lockStderr(&.{}, graph.stderr_mode) catch {}; | |
| 978 | process.exit(code); | |
| 979 | } | |
| 980 | ||
| 981 | const PrintNode = struct { | |
| 982 | parent: ?*PrintNode, | |
| 983 | last: bool = false, | |
| 984 | }; | |
| 985 | ||
| 986 | fn printPrefix(node: *PrintNode, stderr: Io.Terminal) !void { | |
| 987 | const parent = node.parent orelse return; | |
| 988 | const writer = stderr.writer; | |
| 989 | if (parent.parent == null) return; | |
| 990 | try printPrefix(parent, stderr); | |
| 991 | if (parent.last) { | |
| 992 | try writer.writeAll(" "); | |
| 993 | } else { | |
| 994 | try writer.writeAll(switch (stderr.mode) { | |
| 995 | .escape_codes => "\x1B\x28\x30\x78\x1B\x28\x42 ", // │ | |
| 996 | else => "| ", | |
| 997 | }); | |
| 998 | } | |
| 999 | } | |
| 1000 | ||
| 1001 | fn printChildNodePrefix(stderr: Io.Terminal) !void { | |
| 1002 | try stderr.writer.writeAll(switch (stderr.mode) { | |
| 1003 | .escape_codes => "\x1B\x28\x30\x6d\x71\x1B\x28\x42 ", // └─ | |
| 1004 | else => "+- ", | |
| 1005 | }); | |
| 1006 | } | |
| 1007 | ||
| 1008 | fn printStepStatus(s: *Step, stderr: Io.Terminal, run: *const Run) !void { | |
| 1009 | const writer = stderr.writer; | |
| 1010 | switch (s.state) { | |
| 1011 | .precheck_unstarted => unreachable, | |
| 1012 | .precheck_started => unreachable, | |
| 1013 | .precheck_done => unreachable, | |
| 1014 | ||
| 1015 | .dependency_failure => { | |
| 1016 | try stderr.setColor(.dim); | |
| 1017 | try writer.writeAll(" transitive failure\n"); | |
| 1018 | try stderr.setColor(.reset); | |
| 1019 | }, | |
| 1020 | ||
| 1021 | .success => { | |
| 1022 | try stderr.setColor(.green); | |
| 1023 | if (s.result_cached) { | |
| 1024 | try writer.writeAll(" cached"); | |
| 1025 | } else if (s.test_results.test_count > 0) { | |
| 1026 | const pass_count = s.test_results.passCount(); | |
| 1027 | assert(s.test_results.test_count == pass_count + s.test_results.skip_count); | |
| 1028 | try writer.print(" {d} pass", .{pass_count}); | |
| 1029 | if (s.test_results.skip_count > 0) { | |
| 1030 | try stderr.setColor(.reset); | |
| 1031 | try writer.writeAll(", "); | |
| 1032 | try stderr.setColor(.yellow); | |
| 1033 | try writer.print("{d} skip", .{s.test_results.skip_count}); | |
| 1034 | } | |
| 1035 | try stderr.setColor(.reset); | |
| 1036 | try writer.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1037 | } else { | |
| 1038 | try writer.writeAll(" success"); | |
| 1039 | } | |
| 1040 | try stderr.setColor(.reset); | |
| 1041 | if (s.result_duration_ns) |ns| { | |
| 1042 | try stderr.setColor(.dim); | |
| 1043 | if (ns >= std.time.ns_per_min) { | |
| 1044 | try writer.print(" {d}m", .{ns / std.time.ns_per_min}); | |
| 1045 | } else if (ns >= std.time.ns_per_s) { | |
| 1046 | try writer.print(" {d}s", .{ns / std.time.ns_per_s}); | |
| 1047 | } else if (ns >= std.time.ns_per_ms) { | |
| 1048 | try writer.print(" {d}ms", .{ns / std.time.ns_per_ms}); | |
| 1049 | } else if (ns >= std.time.ns_per_us) { | |
| 1050 | try writer.print(" {d}us", .{ns / std.time.ns_per_us}); | |
| 1051 | } else { | |
| 1052 | try writer.print(" {d}ns", .{ns}); | |
| 1053 | } | |
| 1054 | try stderr.setColor(.reset); | |
| 1055 | } | |
| 1056 | if (s.result_peak_rss != 0) { | |
| 1057 | const rss = s.result_peak_rss; | |
| 1058 | try stderr.setColor(.dim); | |
| 1059 | if (rss >= 1000_000_000) { | |
| 1060 | try writer.print(" MaxRSS:{d}G", .{rss / 1000_000_000}); | |
| 1061 | } else if (rss >= 1000_000) { | |
| 1062 | try writer.print(" MaxRSS:{d}M", .{rss / 1000_000}); | |
| 1063 | } else if (rss >= 1000) { | |
| 1064 | try writer.print(" MaxRSS:{d}K", .{rss / 1000}); | |
| 1065 | } else { | |
| 1066 | try writer.print(" MaxRSS:{d}B", .{rss}); | |
| 1067 | } | |
| 1068 | try stderr.setColor(.reset); | |
| 1069 | } | |
| 1070 | try writer.writeAll("\n"); | |
| 1071 | }, | |
| 1072 | .skipped => { | |
| 1073 | try stderr.setColor(.yellow); | |
| 1074 | try writer.writeAll(" skipped\n"); | |
| 1075 | try stderr.setColor(.reset); | |
| 1076 | }, | |
| 1077 | .skipped_oom => { | |
| 1078 | try stderr.setColor(.yellow); | |
| 1079 | try writer.writeAll(" skipped (not enough memory)"); | |
| 1080 | try stderr.setColor(.dim); | |
| 1081 | try writer.print(" upper bound of {d} exceeded runner limit ({d})\n", .{ s.max_rss, run.available_rss }); | |
| 1082 | try stderr.setColor(.reset); | |
| 1083 | }, | |
| 1084 | .failure => { | |
| 1085 | try printStepFailure(s, stderr, false); | |
| 1086 | try stderr.setColor(.reset); | |
| 1087 | }, | |
| 1088 | } | |
| 1089 | } | |
| 1090 | ||
| 1091 | fn printStepFailure(s: *Step, stderr: Io.Terminal, dim: bool) !void { | |
| 1092 | const w = stderr.writer; | |
| 1093 | if (s.result_error_bundle.errorMessageCount() > 0) { | |
| 1094 | try stderr.setColor(.red); | |
| 1095 | try w.print(" {d} errors\n", .{ | |
| 1096 | s.result_error_bundle.errorMessageCount(), | |
| 1097 | }); | |
| 1098 | } else if (!s.test_results.isSuccess()) { | |
| 1099 | // These first values include all of the test "statuses". Every test is either passsed, | |
| 1100 | // skipped, failed, crashed, or timed out. | |
| 1101 | try stderr.setColor(.green); | |
| 1102 | try w.print(" {d} pass", .{s.test_results.passCount()}); | |
| 1103 | try stderr.setColor(.reset); | |
| 1104 | if (dim) try stderr.setColor(.dim); | |
| 1105 | if (s.test_results.skip_count > 0) { | |
| 1106 | try w.writeAll(", "); | |
| 1107 | try stderr.setColor(.yellow); | |
| 1108 | try w.print("{d} skip", .{s.test_results.skip_count}); | |
| 1109 | try stderr.setColor(.reset); | |
| 1110 | if (dim) try stderr.setColor(.dim); | |
| 1111 | } | |
| 1112 | if (s.test_results.fail_count > 0) { | |
| 1113 | try w.writeAll(", "); | |
| 1114 | try stderr.setColor(.red); | |
| 1115 | try w.print("{d} fail", .{s.test_results.fail_count}); | |
| 1116 | try stderr.setColor(.reset); | |
| 1117 | if (dim) try stderr.setColor(.dim); | |
| 1118 | } | |
| 1119 | if (s.test_results.crash_count > 0) { | |
| 1120 | try w.writeAll(", "); | |
| 1121 | try stderr.setColor(.red); | |
| 1122 | try w.print("{d} crash", .{s.test_results.crash_count}); | |
| 1123 | try stderr.setColor(.reset); | |
| 1124 | if (dim) try stderr.setColor(.dim); | |
| 1125 | } | |
| 1126 | if (s.test_results.timeout_count > 0) { | |
| 1127 | try w.writeAll(", "); | |
| 1128 | try stderr.setColor(.red); | |
| 1129 | try w.print("{d} timeout", .{s.test_results.timeout_count}); | |
| 1130 | try stderr.setColor(.reset); | |
| 1131 | if (dim) try stderr.setColor(.dim); | |
| 1132 | } | |
| 1133 | try w.print(" ({d} total)", .{s.test_results.test_count}); | |
| 1134 | ||
| 1135 | // Memory leaks are intentionally written after the total, because is isn't a test *status*, | |
| 1136 | // but just a flag that any tests -- even passed ones -- can have. We also use a different | |
| 1137 | // separator, so it looks like: | |
| 1138 | // 2 pass, 1 skip, 2 fail (5 total); 2 leaks | |
| 1139 | if (s.test_results.leak_count > 0) { | |
| 1140 | try w.writeAll("; "); | |
| 1141 | try stderr.setColor(.red); | |
| 1142 | try w.print("{d} leaks", .{s.test_results.leak_count}); | |
| 1143 | try stderr.setColor(.reset); | |
| 1144 | if (dim) try stderr.setColor(.dim); | |
| 1145 | } | |
| 1146 | ||
| 1147 | // It's usually not helpful to know how many error logs there were because they tend to | |
| 1148 | // just come with other errors (e.g. crashes and leaks print stack traces, and clean | |
| 1149 | // failures print error traces). So only mention them if they're the only thing causing | |
| 1150 | // the failure. | |
| 1151 | const show_err_logs: bool = show: { | |
| 1152 | var alt_results = s.test_results; | |
| 1153 | alt_results.log_err_count = 0; | |
| 1154 | break :show alt_results.isSuccess(); | |
| 1155 | }; | |
| 1156 | if (show_err_logs) { | |
| 1157 | try w.writeAll("; "); | |
| 1158 | try stderr.setColor(.red); | |
| 1159 | try w.print("{d} error logs", .{s.test_results.log_err_count}); | |
| 1160 | try stderr.setColor(.reset); | |
| 1161 | if (dim) try stderr.setColor(.dim); | |
| 1162 | } | |
| 1163 | ||
| 1164 | try w.writeAll("\n"); | |
| 1165 | } else if (s.result_error_msgs.items.len > 0) { | |
| 1166 | try stderr.setColor(.red); | |
| 1167 | try w.writeAll(" failure\n"); | |
| 1168 | } else { | |
| 1169 | assert(s.result_stderr.len > 0); | |
| 1170 | try stderr.setColor(.red); | |
| 1171 | try w.writeAll(" w\n"); | |
| 1172 | } | |
| 1173 | } | |
| 1174 | ||
| 1175 | fn printTreeStep( | |
| 1176 | b: *std.Build, | |
| 1177 | s: *Step, | |
| 1178 | run: *const Run, | |
| 1179 | stderr: Io.Terminal, | |
| 1180 | parent_node: *PrintNode, | |
| 1181 | step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), | |
| 1182 | ) !void { | |
| 1183 | const writer = stderr.writer; | |
| 1184 | const first = step_stack.swapRemove(s); | |
| 1185 | const summary = run.summary; | |
| 1186 | const skip = switch (summary) { | |
| 1187 | .none, .line => unreachable, | |
| 1188 | .all => false, | |
| 1189 | .new => s.result_cached, | |
| 1190 | .failures => s.state == .success, | |
| 1191 | }; | |
| 1192 | if (skip) return; | |
| 1193 | try printPrefix(parent_node, stderr); | |
| 1194 | ||
| 1195 | if (parent_node.parent != null) { | |
| 1196 | if (parent_node.last) { | |
| 1197 | try printChildNodePrefix(stderr); | |
| 1198 | } else { | |
| 1199 | try writer.writeAll(switch (stderr.mode) { | |
| 1200 | .escape_codes => "\x1B\x28\x30\x74\x71\x1B\x28\x42 ", // ├─ | |
| 1201 | else => "+- ", | |
| 1202 | }); | |
| 1203 | } | |
| 1204 | } | |
| 1205 | ||
| 1206 | if (!first) try stderr.setColor(.dim); | |
| 1207 | ||
| 1208 | // dep_prefix omitted here because it is redundant with the tree. | |
| 1209 | try writer.writeAll(s.name); | |
| 1210 | ||
| 1211 | if (first) { | |
| 1212 | try printStepStatus(s, stderr, run); | |
| 1213 | ||
| 1214 | const last_index = if (summary == .all) s.dependencies.items.len -| 1 else blk: { | |
| 1215 | var i: usize = s.dependencies.items.len; | |
| 1216 | while (i > 0) { | |
| 1217 | i -= 1; | |
| 1218 | ||
| 1219 | const step = s.dependencies.items[i]; | |
| 1220 | const found = switch (summary) { | |
| 1221 | .all, .line, .none => unreachable, | |
| 1222 | .failures => step.state != .success, | |
| 1223 | .new => !step.result_cached, | |
| 1224 | }; | |
| 1225 | if (found) break :blk i; | |
| 1226 | } | |
| 1227 | break :blk s.dependencies.items.len -| 1; | |
| 1228 | }; | |
| 1229 | for (s.dependencies.items, 0..) |dep, i| { | |
| 1230 | var print_node: PrintNode = .{ | |
| 1231 | .parent = parent_node, | |
| 1232 | .last = i == last_index, | |
| 1233 | }; | |
| 1234 | try printTreeStep(b, dep, run, stderr, &print_node, step_stack); | |
| 1235 | } | |
| 1236 | } else { | |
| 1237 | if (s.dependencies.items.len == 0) { | |
| 1238 | try writer.writeAll(" (reused)\n"); | |
| 1239 | } else { | |
| 1240 | try writer.print(" (+{d} more reused dependencies)\n", .{ | |
| 1241 | s.dependencies.items.len, | |
| 1242 | }); | |
| 1243 | } | |
| 1244 | try stderr.setColor(.reset); | |
| 1245 | } | |
| 1246 | } | |
| 1247 | ||
| 1248 | /// Traverse the dependency graph depth-first and make it undirected by having | |
| 1249 | /// steps know their dependants (they only know dependencies at start). | |
| 1250 | /// Along the way, check that there is no dependency loop, and record the steps | |
| 1251 | /// in traversal order in `step_stack`. | |
| 1252 | /// Each step has its dependencies traversed in random order, this accomplishes | |
| 1253 | /// two things: | |
| 1254 | /// - `step_stack` will be in randomized-depth-first order, so the build runner | |
| 1255 | /// spawns initial steps in a random order | |
| 1256 | /// - each step's `dependants` list is also filled in a random order, so that | |
| 1257 | /// when it finishes executing in `makeStep`, it spawns next steps to run in | |
| 1258 | /// random order | |
| 1259 | fn constructGraphAndCheckForDependencyLoop( | |
| 1260 | gpa: Allocator, | |
| 1261 | b: *std.Build, | |
| 1262 | s: *Step, | |
| 1263 | step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), | |
| 1264 | rand: std.Random, | |
| 1265 | ) !void { | |
| 1266 | switch (s.state) { | |
| 1267 | .precheck_started => { | |
| 1268 | std.debug.print("dependency loop detected:\n {s}\n", .{s.name}); | |
| 1269 | return error.DependencyLoopDetected; | |
| 1270 | }, | |
| 1271 | .precheck_unstarted => { | |
| 1272 | s.state = .precheck_started; | |
| 1273 | ||
| 1274 | try step_stack.ensureUnusedCapacity(gpa, s.dependencies.items.len); | |
| 1275 | ||
| 1276 | // We dupe to avoid shuffling the steps in the summary, it depends | |
| 1277 | // on s.dependencies' order. | |
| 1278 | const deps = gpa.dupe(*Step, s.dependencies.items) catch @panic("OOM"); | |
| 1279 | defer gpa.free(deps); | |
| 1280 | ||
| 1281 | rand.shuffle(*Step, deps); | |
| 1282 | ||
| 1283 | for (deps) |dep| { | |
| 1284 | try step_stack.put(gpa, dep, {}); | |
| 1285 | try dep.dependants.append(b.allocator, s); | |
| 1286 | constructGraphAndCheckForDependencyLoop(gpa, b, dep, step_stack, rand) catch |err| { | |
| 1287 | if (err == error.DependencyLoopDetected) { | |
| 1288 | std.debug.print(" {s}\n", .{s.name}); | |
| 1289 | } | |
| 1290 | return err; | |
| 1291 | }; | |
| 1292 | } | |
| 1293 | ||
| 1294 | s.state = .precheck_done; | |
| 1295 | s.pending_deps = @intCast(s.dependencies.items.len); | |
| 1296 | }, | |
| 1297 | .precheck_done => {}, | |
| 1298 | ||
| 1299 | // These don't happen until we actually run the step graph. | |
| 1300 | .dependency_failure => unreachable, | |
| 1301 | .success => unreachable, | |
| 1302 | .failure => unreachable, | |
| 1303 | .skipped => unreachable, | |
| 1304 | .skipped_oom => unreachable, | |
| 1305 | } | |
| 1306 | } | |
| 1307 | ||
| 1308 | /// Runs the "make" function of the single step `s`, updates its state, and then spawns newly-ready | |
| 1309 | /// dependant steps in `group`. If `s` makes an RSS claim (i.e. `s.max_rss != 0`), the caller must | |
| 1310 | /// have already subtracted this value from `run.available_rss`. This function will release the RSS | |
| 1311 | /// claim (i.e. add `s.max_rss` back into `run.available_rss`) and queue any viable memory-blocked | |
| 1312 | /// steps after "make" completes for `s`. | |
| 1313 | fn makeStep( | |
| 1314 | group: *Io.Group, | |
| 1315 | b: *std.Build, | |
| 1316 | s: *Step, | |
| 1317 | root_prog_node: std.Progress.Node, | |
| 1318 | run: *Run, | |
| 1319 | ) Io.Cancelable!void { | |
| 1320 | const graph = b.graph; | |
| 1321 | const io = graph.io; | |
| 1322 | const gpa = run.gpa; | |
| 1323 | ||
| 1324 | { | |
| 1325 | const step_prog_node = root_prog_node.start(s.name, 0); | |
| 1326 | defer step_prog_node.end(); | |
| 1327 | ||
| 1328 | if (run.web_server) |*ws| ws.updateStepStatus(s, .wip); | |
| 1329 | ||
| 1330 | const new_state: Step.State = for (s.dependencies.items) |dep| { | |
| 1331 | switch (@atomicLoad(Step.State, &dep.state, .monotonic)) { | |
| 1332 | .precheck_unstarted => unreachable, | |
| 1333 | .precheck_started => unreachable, | |
| 1334 | .precheck_done => unreachable, | |
| 1335 | ||
| 1336 | .failure, | |
| 1337 | .dependency_failure, | |
| 1338 | .skipped_oom, | |
| 1339 | => break .dependency_failure, | |
| 1340 | ||
| 1341 | .success, .skipped => {}, | |
| 1342 | } | |
| 1343 | } else if (s.make(.{ | |
| 1344 | .progress_node = step_prog_node, | |
| 1345 | .watch = run.watch, | |
| 1346 | .web_server = if (run.web_server) |*ws| ws else null, | |
| 1347 | .unit_test_timeout_ns = run.unit_test_timeout_ns, | |
| 1348 | .gpa = gpa, | |
| 1349 | })) state: { | |
| 1350 | break :state .success; | |
| 1351 | } else |err| switch (err) { | |
| 1352 | error.MakeFailed => .failure, | |
| 1353 | error.MakeSkipped => .skipped, | |
| 1354 | }; | |
| 1355 | ||
| 1356 | @atomicStore(Step.State, &s.state, new_state, .monotonic); | |
| 1357 | ||
| 1358 | switch (new_state) { | |
| 1359 | .precheck_unstarted => unreachable, | |
| 1360 | .precheck_started => unreachable, | |
| 1361 | .precheck_done => unreachable, | |
| 1362 | ||
| 1363 | .failure, | |
| 1364 | .dependency_failure, | |
| 1365 | .skipped_oom, | |
| 1366 | => { | |
| 1367 | if (run.web_server) |*ws| ws.updateStepStatus(s, .failure); | |
| 1368 | std.Progress.setStatus(.failure_working); | |
| 1369 | }, | |
| 1370 | ||
| 1371 | .success, | |
| 1372 | .skipped, | |
| 1373 | => { | |
| 1374 | if (run.web_server) |*ws| ws.updateStepStatus(s, .success); | |
| 1375 | }, | |
| 1376 | } | |
| 1377 | } | |
| 1378 | ||
| 1379 | // No matter the result, we want to display error/warning messages. | |
| 1380 | if (s.result_error_bundle.errorMessageCount() > 0 or | |
| 1381 | s.result_error_msgs.items.len > 0 or | |
| 1382 | s.result_stderr.len > 0) | |
| 1383 | { | |
| 1384 | const stderr = try io.lockStderr(&stdio_buffer_allocation, graph.stderr_mode); | |
| 1385 | defer io.unlockStderr(); | |
| 1386 | printErrorMessages(gpa, s, .{}, stderr.terminal(), run.error_style, run.multiline_errors) catch {}; | |
| 1387 | } | |
| 1388 | ||
| 1389 | if (s.max_rss != 0) { | |
| 1390 | var dispatch_set: std.ArrayList(*Step) = .empty; | |
| 1391 | defer dispatch_set.deinit(gpa); | |
| 1392 | ||
| 1393 | // Release our RSS claim and kick off some blocked steps if possible. We use `dispatch_set` | |
| 1394 | // as a staging buffer to avoid recursing into `makeStep` while `run.max_rss_mutex` is held. | |
| 1395 | { | |
| 1396 | try run.max_rss_mutex.lock(io); | |
| 1397 | defer run.max_rss_mutex.unlock(io); | |
| 1398 | run.available_rss += s.max_rss; | |
| 1399 | dispatch_set.ensureUnusedCapacity(gpa, run.memory_blocked_steps.items.len) catch @panic("OOM"); | |
| 1400 | while (run.memory_blocked_steps.getLast()) |candidate| { | |
| 1401 | if (run.available_rss < candidate.max_rss) break; | |
| 1402 | assert(run.memory_blocked_steps.pop() == candidate); | |
| 1403 | dispatch_set.appendAssumeCapacity(candidate); | |
| 1404 | } | |
| 1405 | } | |
| 1406 | for (dispatch_set.items) |candidate| { | |
| 1407 | group.async(io, makeStep, .{ group, b, candidate, root_prog_node, run }); | |
| 1408 | } | |
| 1409 | } | |
| 1410 | ||
| 1411 | for (s.dependants.items) |dependant| { | |
| 1412 | // `.acq_rel` synchronizes with itself to ensure all dependencies' final states are visible when this hits 0. | |
| 1413 | if (@atomicRmw(u32, &dependant.pending_deps, .Sub, 1, .acq_rel) == 1) { | |
| 1414 | try stepReady(group, b, dependant, root_prog_node, run); | |
| 1415 | } | |
| 1416 | } | |
| 1417 | } | |
| 1418 | ||
| 1419 | fn stepReady( | |
| 1420 | group: *Io.Group, | |
| 1421 | b: *std.Build, | |
| 1422 | s: *Step, | |
| 1423 | root_prog_node: std.Progress.Node, | |
| 1424 | run: *Run, | |
| 1425 | ) !void { | |
| 1426 | const io = b.graph.io; | |
| 1427 | if (s.max_rss != 0) { | |
| 1428 | try run.max_rss_mutex.lock(io); | |
| 1429 | defer run.max_rss_mutex.unlock(io); | |
| 1430 | if (run.available_rss < s.max_rss) { | |
| 1431 | // Running this step right now could possibly exceed the allotted RSS. | |
| 1432 | run.memory_blocked_steps.append(run.gpa, s) catch @panic("OOM"); | |
| 1433 | return; | |
| 1434 | } | |
| 1435 | run.available_rss -= s.max_rss; | |
| 1436 | } | |
| 1437 | group.async(io, makeStep, .{ group, b, s, root_prog_node, run }); | |
| 1438 | } | |
| 1439 | ||
| 1440 | pub fn printErrorMessages( | |
| 1441 | gpa: Allocator, | |
| 1442 | failing_step: *Step, | |
| 1443 | options: std.zig.ErrorBundle.RenderOptions, | |
| 1444 | stderr: Io.Terminal, | |
| 1445 | error_style: ErrorStyle, | |
| 1446 | multiline_errors: MultilineErrors, | |
| 1447 | ) !void { | |
| 1448 | const writer = stderr.writer; | |
| 1449 | if (error_style.verboseContext()) { | |
| 1450 | // Provide context for where these error messages are coming from by | |
| 1451 | // printing the corresponding Step subtree. | |
| 1452 | var step_stack: std.ArrayList(*Step) = .empty; | |
| 1453 | defer step_stack.deinit(gpa); | |
| 1454 | try step_stack.append(gpa, failing_step); | |
| 1455 | while (step_stack.items[step_stack.items.len - 1].dependants.items.len != 0) { | |
| 1456 | try step_stack.append(gpa, step_stack.items[step_stack.items.len - 1].dependants.items[0]); | |
| 1457 | } | |
| 1458 | ||
| 1459 | // Now, `step_stack` has the subtree that we want to print, in reverse order. | |
| 1460 | try stderr.setColor(.dim); | |
| 1461 | var indent: usize = 0; | |
| 1462 | while (step_stack.pop()) |s| : (indent += 1) { | |
| 1463 | if (indent > 0) { | |
| 1464 | try writer.splatByteAll(' ', (indent - 1) * 3); | |
| 1465 | try printChildNodePrefix(stderr); | |
| 1466 | } | |
| 1467 | ||
| 1468 | try writer.writeAll(s.name); | |
| 1469 | ||
| 1470 | if (s == failing_step) { | |
| 1471 | try printStepFailure(s, stderr, true); | |
| 1472 | } else { | |
| 1473 | try writer.writeAll("\n"); | |
| 1474 | } | |
| 1475 | } | |
| 1476 | try stderr.setColor(.reset); | |
| 1477 | } else { | |
| 1478 | // Just print the failing step itself. | |
| 1479 | try stderr.setColor(.dim); | |
| 1480 | try writer.writeAll(failing_step.name); | |
| 1481 | try printStepFailure(failing_step, stderr, true); | |
| 1482 | try stderr.setColor(.reset); | |
| 1483 | } | |
| 1484 | ||
| 1485 | if (failing_step.result_stderr.len > 0) { | |
| 1486 | try writer.writeAll(failing_step.result_stderr); | |
| 1487 | if (!mem.endsWith(u8, failing_step.result_stderr, "\n")) { | |
| 1488 | try writer.writeAll("\n"); | |
| 1489 | } | |
| 1490 | } | |
| 1491 | ||
| 1492 | try failing_step.result_error_bundle.renderToTerminal(options, stderr); | |
| 1493 | ||
| 1494 | for (failing_step.result_error_msgs.items) |msg| { | |
| 1495 | try stderr.setColor(.red); | |
| 1496 | try writer.writeAll("error:"); | |
| 1497 | try stderr.setColor(.reset); | |
| 1498 | if (std.mem.indexOfScalar(u8, msg, '\n') == null) { | |
| 1499 | try writer.print(" {s}\n", .{msg}); | |
| 1500 | } else switch (multiline_errors) { | |
| 1501 | .indent => { | |
| 1502 | var it = std.mem.splitScalar(u8, msg, '\n'); | |
| 1503 | try writer.print(" {s}\n", .{it.first()}); | |
| 1504 | while (it.next()) |line| { | |
| 1505 | try writer.print(" {s}\n", .{line}); | |
| 1506 | } | |
| 1507 | }, | |
| 1508 | .newline => try writer.print("\n{s}\n", .{msg}), | |
| 1509 | .none => try writer.print(" {s}\n", .{msg}), | |
| 1510 | } | |
| 1511 | } | |
| 1512 | ||
| 1513 | if (error_style.verboseContext()) { | |
| 1514 | if (failing_step.result_failed_command) |cmd_str| { | |
| 1515 | try stderr.setColor(.red); | |
| 1516 | try writer.writeAll("failed command: "); | |
| 1517 | try stderr.setColor(.reset); | |
| 1518 | try writer.writeAll(cmd_str); | |
| 1519 | try writer.writeByte('\n'); | |
| 1520 | } | |
| 1521 | } | |
| 1522 | ||
| 1523 | try writer.writeByte('\n'); | |
| 1524 | } | |
| 1525 | ||
| 1526 | fn printSteps(builder: *std.Build, w: *Writer) !void { | |
| 1527 | const arena = builder.graph.arena; | |
| 1528 | for (builder.top_level_steps.values()) |top_level_step| { | |
| 1529 | const name = if (&top_level_step.step == builder.default_step) | |
| 1530 | try fmt.allocPrint(arena, "{s} (default)", .{top_level_step.step.name}) | |
| 1531 | else | |
| 1532 | top_level_step.step.name; | |
| 1533 | try w.print(" {s:<28} {s}\n", .{ name, top_level_step.description }); | |
| 1534 | } | |
| 1535 | } | |
| 1536 | ||
| 1537 | fn printUsage(b: *std.Build, w: *Writer) !void { | |
| 1538 | const arena = b.graph.arena; | |
| 1539 | ||
| 1540 | try w.print( | |
| 1541 | \\Usage: {s} build [steps] [options] | |
| 1542 | \\ | |
| 1543 | \\Steps: | |
| 1544 | \\ | |
| 1545 | , .{b.graph.zig_exe}); | |
| 1546 | try printSteps(b, w); | |
| 1547 | try w.writeAll( | |
| 1548 | \\ | |
| 1549 | \\Project-Specific Options: | |
| 1550 | \\ | |
| 1551 | ); | |
| 1552 | ||
| 1553 | if (b.available_options_list.items.len == 0) { | |
| 1554 | try w.print(" (none)\n", .{}); | |
| 1555 | } else { | |
| 1556 | for (b.available_options_list.items) |option| { | |
| 1557 | const name = try fmt.allocPrint(arena, " -D{s}=[{t}]", .{ option.name, option.type_id }); | |
| 1558 | try w.print("{s:<30} {s}\n", .{ name, option.description }); | |
| 1559 | if (option.enum_options) |enum_options| { | |
| 1560 | const padding: [33]u8 = @splat(' '); | |
| 1561 | try w.writeAll(padding ++ "Supported Values:\n"); | |
| 1562 | for (enum_options) |enum_option| { | |
| 1563 | try w.print(padding ++ " {s}\n", .{enum_option}); | |
| 1564 | } | |
| 1565 | } | |
| 1566 | } | |
| 1567 | } | |
| 1568 | ||
| 1569 | try w.writeAll( | |
| 1570 | \\ | |
| 1571 | \\System Integration Options: | |
| 1572 | \\ --search-prefix [path] Add a path to look for binaries, libraries, headers | |
| 1573 | \\ --sysroot [path] Set the system root directory (usually /) | |
| 1574 | \\ --libc [file] Provide a file which specifies libc paths | |
| 1575 | \\ | |
| 1576 | \\ --system [pkgdir] Disable package fetching; enable all integrations | |
| 1577 | \\ -fsys=[name] Enable a system integration | |
| 1578 | \\ -fno-sys=[name] Disable a system integration | |
| 1579 | \\ | |
| 1580 | \\ -fdarling, -fno-darling Integration with system-installed Darling to | |
| 1581 | \\ execute macOS programs on Linux hosts | |
| 1582 | \\ (default: no) | |
| 1583 | \\ -fqemu, -fno-qemu Integration with system-installed QEMU to execute | |
| 1584 | \\ foreign-architecture programs on Linux hosts | |
| 1585 | \\ (default: no) | |
| 1586 | \\ --libc-runtimes [path] Enhances QEMU integration by providing dynamic libc | |
| 1587 | \\ (e.g. glibc or musl) built for multiple foreign | |
| 1588 | \\ architectures, allowing execution of non-native | |
| 1589 | \\ programs that link with libc. | |
| 1590 | \\ -frosetta, -fno-rosetta Rely on Rosetta to execute x86_64 programs on | |
| 1591 | \\ ARM64 macOS hosts. (default: no) | |
| 1592 | \\ -fwasmtime, -fno-wasmtime Integration with system-installed wasmtime to | |
| 1593 | \\ execute WASI binaries. (default: no) | |
| 1594 | \\ -fwine, -fno-wine Integration with system-installed Wine to execute | |
| 1595 | \\ Windows programs on Linux hosts. (default: no) | |
| 1596 | \\ | |
| 1597 | \\ Available System Integrations: Enabled: | |
| 1598 | \\ | |
| 1599 | ); | |
| 1600 | if (b.graph.system_library_options.entries.len == 0) { | |
| 1601 | try w.writeAll(" (none) -\n"); | |
| 1602 | } else { | |
| 1603 | for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| { | |
| 1604 | const status = switch (v) { | |
| 1605 | .declared_enabled => "yes", | |
| 1606 | .declared_disabled => "no", | |
| 1607 | .user_enabled, .user_disabled => unreachable, // already emitted error | |
| 1608 | }; | |
| 1609 | try w.print(" {s:<43} {s}\n", .{ k, status }); | |
| 1610 | } | |
| 1611 | } | |
| 1612 | ||
| 1613 | try w.writeAll( | |
| 1614 | \\ | |
| 1615 | \\General Options: | |
| 1616 | \\ -h, --help Print this help and exit | |
| 1617 | \\ -l, --list-steps Print available steps | |
| 1618 | \\ | |
| 1619 | \\ -p, --prefix [path] Where to install files (default: zig-out) | |
| 1620 | \\ --prefix-lib-dir [path] Where to install libraries | |
| 1621 | \\ --prefix-exe-dir [path] Where to install executables | |
| 1622 | \\ --prefix-include-dir [path] Where to install C header files | |
| 1623 | \\ --release[=mode] Request release mode, optionally specifying a | |
| 1624 | \\ preferred optimization mode: fast, safe, small | |
| 1625 | \\ | |
| 1626 | \\ --verbose Print commands before executing them | |
| 1627 | \\ --color [auto|off|on] Enable or disable colored error messages | |
| 1628 | \\ --error-style [style] Control how build errors are printed | |
| 1629 | \\ verbose (Default) Report errors with full context | |
| 1630 | \\ minimal Report errors after summary, excluding context like command lines | |
| 1631 | \\ verbose_clear Like 'verbose', but clear the terminal at the start of each update | |
| 1632 | \\ minimal_clear Like 'minimal', but clear the terminal at the start of each update | |
| 1633 | \\ --multiline-errors [style] Control how multi-line error messages are printed | |
| 1634 | \\ indent (Default) Indent non-initial lines to align with initial line | |
| 1635 | \\ newline Include a leading newline so that the error message is on its own lines | |
| 1636 | \\ none Print as usual so the first line is misaligned | |
| 1637 | \\ --summary [mode] Control the printing of the build summary | |
| 1638 | \\ all Print the build summary in its entirety | |
| 1639 | \\ new Omit cached steps | |
| 1640 | \\ failures (Default if short-lived) Only print failed steps | |
| 1641 | \\ line (Default if long-lived) Only print the single-line summary | |
| 1642 | \\ none Do not print the build summary | |
| 1643 | \\ -j<N> Limit concurrent jobs (default is to use all CPU cores) | |
| 1644 | \\ --maxrss <bytes> Limit memory usage (default is to use available memory) | |
| 1645 | \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss | |
| 1646 | \\ --test-timeout <timeout> Limit execution time of unit tests, terminating if exceeded. | |
| 1647 | \\ The timeout must include a unit: ns, us, ms, s, m, h | |
| 1648 | \\ --watch Continuously rebuild when source files are modified | |
| 1649 | \\ --debounce <ms> Delay before rebuilding after changed file detected | |
| 1650 | \\ --webui[=ip] Enable the web interface on the given IP address | |
| 1651 | \\ --fuzz[=limit] Continuously search for unit test failures with an optional | |
| 1652 | \\ limit to the max number of iterations. The argument supports | |
| 1653 | \\ an optional 'K', 'M', or 'G' suffix (e.g. '10K'). Implies | |
| 1654 | \\ '--webui' when no limit is specified. | |
| 1655 | \\ --time-report Force full rebuild and provide detailed information on | |
| 1656 | \\ compilation time of Zig source code (implies '--webui') | |
| 1657 | \\ -fincremental Enable incremental compilation | |
| 1658 | \\ -fno-incremental Disable incremental compilation | |
| 1659 | \\ | |
| 1660 | \\Package Management Options: | |
| 1661 | \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit | |
| 1662 | \\ needed (Default) Lazy dependencies are fetched as needed | |
| 1663 | \\ all Lazy dependencies are always fetched | |
| 1664 | \\ --fork=[path] Override one or more projects from dependency tree | |
| 1665 | \\ | |
| 1666 | \\Advanced Options: | |
| 1667 | \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error | |
| 1668 | \\ -fno-reference-trace Disable reference trace | |
| 1669 | \\ -fallow-so-scripts Allows .so files to be GNU ld scripts | |
| 1670 | \\ -fno-allow-so-scripts (default) .so files must be ELF files | |
| 1671 | \\ --build-file [file] Override path to build.zig | |
| 1672 | \\ --cache-dir [path] Override path to local Zig cache directory | |
| 1673 | \\ --global-cache-dir [path] Override path to global Zig cache directory | |
| 1674 | \\ --zig-lib-dir [arg] Override path to Zig lib directory | |
| 1675 | \\ --build-runner [file] Override path to build runner | |
| 1676 | \\ --seed [integer] For shuffling dependency traversal order (default: random) | |
| 1677 | \\ --build-id[=style] At a minor link-time expense, embeds a build ID in binaries | |
| 1678 | \\ fast 8-byte non-cryptographic hash (COFF, ELF, WASM) | |
| 1679 | \\ sha1, tree 20-byte cryptographic hash (ELF, WASM) | |
| 1680 | \\ md5 16-byte cryptographic hash (ELF) | |
| 1681 | \\ uuid 16-byte random UUID (ELF, WASM) | |
| 1682 | \\ 0x[hexstring] Constant ID, maximum 32 bytes (ELF, WASM) | |
| 1683 | \\ none (default) No build ID | |
| 1684 | \\ --debug-log [scope] Enable debugging the compiler | |
| 1685 | \\ --debug-pkg-config Fail if unknown pkg-config flags encountered | |
| 1686 | \\ --debug-rt Debug compiler runtime libraries | |
| 1687 | \\ --verbose-link Enable compiler debug output for linking | |
| 1688 | \\ --verbose-air Enable compiler debug output for Zig AIR | |
| 1689 | \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR | |
| 1690 | \\ --verbose-llvm-bc=[file] Enable compiler debug output for LLVM BC | |
| 1691 | \\ --verbose-cimport Enable compiler debug output for C imports | |
| 1692 | \\ --verbose-cc Enable compiler debug output for C compilation | |
| 1693 | \\ --verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features | |
| 1694 | \\ | |
| 1695 | ); | |
| 1696 | } | |
| 1697 | ||
| 1698 | fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { | |
| 1699 | if (idx.* >= args.len) return null; | |
| 1700 | defer idx.* += 1; | |
| 1701 | return args[idx.*]; | |
| 1702 | } | |
| 1703 | ||
| 1704 | fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { | |
| 1705 | return nextArg(args, idx) orelse { | |
| 1706 | std.debug.print("expected argument after '{s}'\n access the help menu with 'zig build -h'\n", .{args[idx.* - 1]}); | |
| 1707 | process.exit(1); | |
| 1708 | }; | |
| 1709 | } | |
| 1710 | ||
| 1711 | fn argsRest(args: []const [:0]const u8, idx: usize) ?[]const [:0]const u8 { | |
| 1712 | if (idx >= args.len) return null; | |
| 1713 | return args[idx..]; | |
| 1714 | } | |
| 1715 | ||
| 1716 | const Color = std.zig.Color; | |
| 1717 | const ErrorStyle = enum { | |
| 1718 | verbose, | |
| 1719 | minimal, | |
| 1720 | verbose_clear, | |
| 1721 | minimal_clear, | |
| 1722 | fn verboseContext(s: ErrorStyle) bool { | |
| 1723 | return switch (s) { | |
| 1724 | .verbose, .verbose_clear => true, | |
| 1725 | .minimal, .minimal_clear => false, | |
| 1726 | }; | |
| 1727 | } | |
| 1728 | fn clearOnUpdate(s: ErrorStyle) bool { | |
| 1729 | return switch (s) { | |
| 1730 | .verbose, .minimal => false, | |
| 1731 | .verbose_clear, .minimal_clear => true, | |
| 1732 | }; | |
| 1733 | } | |
| 1734 | }; | |
| 1735 | const MultilineErrors = enum { indent, newline, none }; | |
| 1736 | const Summary = enum { all, new, failures, line, none }; | |
| 1737 | ||
| 1738 | fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { | |
| 1739 | std.debug.print(f ++ "\n access the help menu with 'zig build -h'\n", args); | |
| 1740 | process.exit(1); | |
| 1741 | } | |
| 1742 | ||
| 1743 | fn validateSystemLibraryOptions(b: *std.Build) void { | |
| 1744 | var bad = false; | |
| 1745 | for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| { | |
| 1746 | switch (v) { | |
| 1747 | .user_disabled, .user_enabled => { | |
| 1748 | // The user tried to enable or disable a system library integration, but | |
| 1749 | // the build script did not recognize that option. | |
| 1750 | std.debug.print("system library name not recognized by build script: '{s}'\n", .{k}); | |
| 1751 | bad = true; | |
| 1752 | }, | |
| 1753 | .declared_disabled, .declared_enabled => {}, | |
| 1754 | } | |
| 1755 | } | |
| 1756 | if (bad) { | |
| 1757 | std.debug.print(" access the help menu with 'zig build -h'\n", .{}); | |
| 1758 | process.exit(1); | |
| 1759 | } | |
| 1760 | } | |
| 1761 | ||
| 1762 | /// Starting from all top-level steps in `b`, traverses the entire step graph | |
| 1763 | /// and adds all step dependencies implied by module graphs. | |
| 1764 | fn createModuleDependencies(b: *std.Build) Allocator.Error!void { | |
| 1765 | const arena = b.graph.arena; | |
| 1766 | ||
| 1767 | var all_steps: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty; | |
| 1768 | var next_step_idx: usize = 0; | |
| 1769 | ||
| 1770 | try all_steps.ensureUnusedCapacity(arena, b.top_level_steps.count()); | |
| 1771 | for (b.top_level_steps.values()) |tls| { | |
| 1772 | all_steps.putAssumeCapacityNoClobber(&tls.step, {}); | |
| 1773 | } | |
| 1774 | ||
| 1775 | while (next_step_idx < all_steps.count()) { | |
| 1776 | const step = all_steps.keys()[next_step_idx]; | |
| 1777 | next_step_idx += 1; | |
| 1778 | ||
| 1779 | // Set up any implied dependencies for this step. It's important that we do this first, so | |
| 1780 | // that the loop below discovers steps implied by the module graph. | |
| 1781 | try createModuleDependenciesForStep(step); | |
| 1782 | ||
| 1783 | try all_steps.ensureUnusedCapacity(arena, step.dependencies.items.len); | |
| 1784 | for (step.dependencies.items) |other_step| { | |
| 1785 | all_steps.putAssumeCapacity(other_step, {}); | |
| 1786 | } | |
| 1787 | } | |
| 1788 | } | |
| 1789 | ||
| 1790 | /// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which | |
| 1791 | /// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`. | |
| 1792 | fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { | |
| 1793 | const root_module = if (step.cast(Step.Compile)) |cs| root: { | |
| 1794 | break :root cs.root_module; | |
| 1795 | } else return; // not a compile step so no module dependencies | |
| 1796 | ||
| 1797 | // Starting from `root_module`, discover all modules in this graph. | |
| 1798 | const modules = root_module.getGraph().modules; | |
| 1799 | ||
| 1800 | // For each of those modules, set up the implied step dependencies. | |
| 1801 | for (modules) |mod| { | |
| 1802 | if (mod.root_source_file) |lp| lp.addStepDependencies(step); | |
| 1803 | for (mod.include_dirs.items) |include_dir| switch (include_dir) { | |
| 1804 | .path, | |
| 1805 | .path_system, | |
| 1806 | .path_after, | |
| 1807 | .framework_path, | |
| 1808 | .framework_path_system, | |
| 1809 | .embed_path, | |
| 1810 | => |lp| lp.addStepDependencies(step), | |
| 1811 | ||
| 1812 | .other_step => |other| { | |
| 1813 | other.getEmittedIncludeTree().addStepDependencies(step); | |
| 1814 | step.dependOn(&other.step); | |
| 1815 | }, | |
| 1816 | ||
| 1817 | .config_header_step => |other| step.dependOn(&other.step), | |
| 1818 | }; | |
| 1819 | for (mod.lib_paths.items) |lp| lp.addStepDependencies(step); | |
| 1820 | for (mod.rpaths.items) |rpath| switch (rpath) { | |
| 1821 | .lazy_path => |lp| lp.addStepDependencies(step), | |
| 1822 | .special => {}, | |
| 1823 | }; | |
| 1824 | for (mod.link_objects.items) |link_object| switch (link_object) { | |
| 1825 | .static_path, | |
| 1826 | .assembly_file, | |
| 1827 | => |lp| lp.addStepDependencies(step), | |
| 1828 | .other_step => |other| step.dependOn(&other.step), | |
| 1829 | .system_lib => {}, | |
| 1830 | .c_source_file => |source| source.file.addStepDependencies(step), | |
| 1831 | .c_source_files => |source_files| source_files.root.addStepDependencies(step), | |
| 1832 | .win32_resource_file => |rc_source| { | |
| 1833 | rc_source.file.addStepDependencies(step); | |
| 1834 | for (rc_source.include_paths) |lp| lp.addStepDependencies(step); | |
| 1835 | }, | |
| 1836 | }; | |
| 1837 | } | |
| 1838 | } | |
| 1839 | ||
| 1840 | var stdio_buffer_allocation: [256]u8 = undefined; | |
| 1841 | var stdout_writer_allocation: Io.File.Writer = undefined; | |
| 1842 | ||
| 1843 | fn initStdoutWriter(io: Io) *Writer { | |
| 1844 | stdout_writer_allocation = Io.File.stdout().writerStreaming(io, &stdio_buffer_allocation); | |
| 1845 | return &stdout_writer_allocation.interface; | |
| 1846 | } | |
| 1847 | ||
| 1848 | fn cleanTmpFiles(io: Io, steps: []const *Step) void { | |
| 1849 | for (steps) |step| { | |
| 1850 | const wf = step.cast(std.Build.Step.WriteFile) orelse continue; | |
| 1851 | if (wf.mode != .tmp) continue; | |
| 1852 | const path = wf.generated_directory.path orelse continue; | |
| 1853 | Io.Dir.cwd().deleteTree(io, path) catch |err| { | |
| 1854 | std.log.warn("failed to delete {s}: {t}", .{ path, err }); | |
| 1855 | }; | |
| 1856 | } | |
| 1857 | } |
lib/compiler/configurer.zig created+1402| ... | ... | @@ -0,0 +1,1402 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | const Color = std.zig.Color; | |
| 6 | const Configuration = std.Build.Configuration; | |
| 7 | const File = std.Io.File; | |
| 8 | const Io = std.Io; | |
| 9 | const Step = std.Build.Step; | |
| 10 | const Writer = std.Io.Writer; | |
| 11 | const assert = std.debug.assert; | |
| 12 | const fatal = std.process.fatal; | |
| 13 | const fmt = std.fmt; | |
| 14 | const log = std.log; | |
| 15 | const mem = std.mem; | |
| 16 | const process = std.process; | |
| 17 | ||
| 18 | pub const root = @import("@build"); | |
| 19 | pub const dependencies = @import("@dependencies"); | |
| 20 | ||
| 21 | pub const std_options: std.Options = .{ | |
| 22 | .side_channels_mitigations = .none, | |
| 23 | .http_disable_tls = true, | |
| 24 | }; | |
| 25 | ||
| 26 | pub fn main(init: process.Init.Minimal) !void { | |
| 27 | var arena_allocator: std.heap.ArenaAllocator = .init(std.heap.page_allocator); | |
| 28 | defer arena_allocator.deinit(); | |
| 29 | const arena = arena_allocator.allocator(); | |
| 30 | ||
| 31 | // The configurer is always short-lived because all it does is serialize | |
| 32 | // the configuration, which is picked up by a separate maker process. | |
| 33 | var threaded: std.Io.Threaded = .init(arena, .{ | |
| 34 | .environ = init.environ, | |
| 35 | .argv0 = .init(init.args), | |
| 36 | }); | |
| 37 | defer threaded.deinit(); | |
| 38 | const io = threaded.io(); | |
| 39 | ||
| 40 | const args = try init.args.toSlice(arena); | |
| 41 | ||
| 42 | var arg_i: usize = 1; // Skip own executable name. | |
| 43 | ||
| 44 | const zig_exe = expectArgOrFatal(args, &arg_i, "--zig"); | |
| 45 | const build_root_sub_path = expectArgOrFatal(args, &arg_i, "--build-root"); | |
| 46 | ||
| 47 | var graph: std.Build.Graph = .{ | |
| 48 | .io = io, | |
| 49 | .arena = arena, | |
| 50 | .environ_map = try init.environ.createMap(arena), | |
| 51 | // TODO get this from parent process instead | |
| 52 | .host = .{ | |
| 53 | .query = .{}, | |
| 54 | .result = try std.zig.system.resolveTargetQuery(io, .{}), | |
| 55 | }, | |
| 56 | .generated_files = .empty, | |
| 57 | .zig_exe = zig_exe, | |
| 58 | ||
| 59 | // Created before running the user's configure script so that some things | |
| 60 | // can be added during script execution such as strings. | |
| 61 | // | |
| 62 | // Use of arena here is load-bearing because `std.Build.dupe` is | |
| 63 | // implemented by string internment, and then returning the interned | |
| 64 | // slice. When the string bytes array is reallocated, that reference | |
| 65 | // must stay alive. | |
| 66 | .wip_configuration = .init(arena), | |
| 67 | }; | |
| 68 | assert(try graph.wip_configuration.addString("") == .empty); | |
| 69 | assert(try graph.wip_configuration.addString("root") == .root); | |
| 70 | ||
| 71 | const cwd: Io.Dir = .cwd(); | |
| 72 | ||
| 73 | const build_root: std.Build.Cache.Path = .{ | |
| 74 | .root_dir = .{ | |
| 75 | .handle = try cwd.openDir(io, build_root_sub_path, .{}), | |
| 76 | .path = build_root_sub_path, | |
| 77 | }, | |
| 78 | }; | |
| 79 | ||
| 80 | const builder = try std.Build.create(&graph, build_root, dependencies.root_deps); | |
| 81 | ||
| 82 | var color: Color = .auto; | |
| 83 | ||
| 84 | while (nextArg(args, &arg_i)) |arg| { | |
| 85 | if (mem.cutPrefix(u8, arg, "-D")) |option_contents| { | |
| 86 | if (option_contents.len == 0) | |
| 87 | fatalWithHint("expected option name after '-D'", .{}); | |
| 88 | if (mem.indexOfScalar(u8, option_contents, '=')) |name_end| { | |
| 89 | const option_name = option_contents[0..name_end]; | |
| 90 | const option_value = option_contents[name_end + 1 ..]; | |
| 91 | if (try builder.addUserInputOption(option_name, option_value)) | |
| 92 | fatal(" access the help menu with 'zig build -h'", .{}); | |
| 93 | } else { | |
| 94 | if (try builder.addUserInputFlag(option_contents)) | |
| 95 | fatal(" access the help menu with 'zig build -h'", .{}); | |
| 96 | } | |
| 97 | } else if (mem.cutPrefix(u8, arg, "-fsys=")) |name| { | |
| 98 | try graph.system_integration_options.put(arena, name, .user_enabled); | |
| 99 | } else if (mem.cutPrefix(u8, arg, "-fno-sys=")) |name| { | |
| 100 | try graph.system_integration_options.put(arena, name, .user_disabled); | |
| 101 | } else if (mem.eql(u8, arg, "--release")) { | |
| 102 | graph.release_mode = .any; | |
| 103 | } else if (mem.cutPrefix(u8, arg, "--release=")) |rest| { | |
| 104 | graph.release_mode = std.meta.stringToEnum(std.Build.ReleaseMode, rest) orelse { | |
| 105 | fatalWithHint("expected --release=[off|any|fast|safe|small]; found: {s}", .{arg}); | |
| 106 | }; | |
| 107 | } else if (mem.cutPrefix(u8, arg, "--color=")) |rest| { | |
| 108 | color = std.meta.stringToEnum(Color, rest) orelse | |
| 109 | fatalWithHint("expected --color=[auto|on|off]; found: {s}", .{arg}); | |
| 110 | } else if (mem.eql(u8, arg, "--system")) { | |
| 111 | // The usage text shows another argument after this parameter | |
| 112 | // but it is handled by the parent process. The build runner | |
| 113 | // only sees this flag. | |
| 114 | graph.system_package_mode = true; | |
| 115 | } else if (mem.eql(u8, arg, "--verbose")) { | |
| 116 | graph.verbose = true; | |
| 117 | } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| { | |
| 118 | graph.cache_poison = std.meta.stringToEnum(std.Build.Graph.CachePoison, rest) orelse | |
| 119 | fatalWithHint("expected --cache-poison=[pure|poisoned|disallowed|ignored]; found: {s}", .{arg}); | |
| 120 | } else if (mem.eql(u8, arg, "--search-prefix")) { | |
| 121 | try graph.search_prefixes.append(arena, nextArgOrFatal(args, &arg_i)); | |
| 122 | } else { | |
| 123 | fatalWithHint("unrecognized argument: {s}", .{arg}); | |
| 124 | } | |
| 125 | } | |
| 126 | ||
| 127 | const NO_COLOR = std.zig.EnvVar.NO_COLOR.isSet(&graph.environ_map); | |
| 128 | const CLICOLOR_FORCE = std.zig.EnvVar.CLICOLOR_FORCE.isSet(&graph.environ_map); | |
| 129 | ||
| 130 | graph.stderr_mode = switch (color) { | |
| 131 | .auto => try .detect(io, .stderr(), NO_COLOR, CLICOLOR_FORCE), | |
| 132 | .on => .escape_codes, | |
| 133 | .off => .no_color, | |
| 134 | }; | |
| 135 | ||
| 136 | try builder.runBuild(root); | |
| 137 | ||
| 138 | if (builder.validateUserInputDidItFail()) { | |
| 139 | fatal(" access the help menu with 'zig build -h'", .{}); | |
| 140 | } | |
| 141 | ||
| 142 | try serializePackageOptions(builder, &graph.wip_configuration); | |
| 143 | try serializeSystemIntegrationOptions(&graph, &graph.wip_configuration); | |
| 144 | ||
| 145 | var stdout_buffer: [1024]u8 = undefined; | |
| 146 | var file_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); | |
| 147 | serialize(builder, &graph.wip_configuration, &file_writer.interface) catch |err| switch (err) { | |
| 148 | error.WriteFailed => fatal("failed to write configuration output: {t}", .{file_writer.err.?}), | |
| 149 | error.OutOfMemory => |e| return e, | |
| 150 | }; | |
| 151 | file_writer.flush() catch |err| fatal("failed to write configuration output: {t}", .{err}); | |
| 152 | ||
| 153 | // This executable is short-lived and run in Debug mode, so we'd rather | |
| 154 | // have `zig build` run faster than catch resource leaks in the user's | |
| 155 | // build.zig script (or, frankly, this configure runner), therefore we call | |
| 156 | // exit directly here rather than cleanExit. | |
| 157 | process.exit(0); | |
| 158 | } | |
| 159 | ||
| 160 | const Serialize = struct { | |
| 161 | arena: Allocator, | |
| 162 | wc: *Configuration.Wip, | |
| 163 | module_map: std.AutoArrayHashMapUnmanaged(*std.Build.Module, Configuration.Module.Index) = .empty, | |
| 164 | package_map: std.AutoArrayHashMapUnmanaged(*std.Build, Configuration.Package.Index) = .empty, | |
| 165 | /// Index corresponds to `Configuration.steps` index. | |
| 166 | step_map: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty, | |
| 167 | ||
| 168 | fn builderToPackage(s: *Serialize, b: *std.Build) !Configuration.Package.Index { | |
| 169 | if (b.pkg_hash.len == 0) return .root; | |
| 170 | const arena = s.arena; | |
| 171 | const wc = s.wc; | |
| 172 | const gop = try s.package_map.getOrPut(arena, b); | |
| 173 | if (!gop.found_existing) { | |
| 174 | gop.value_ptr.* = try wc.addExtra(Configuration.Package, .{ | |
| 175 | .hash = try wc.addString(b.pkg_hash), | |
| 176 | .dep_prefix = try wc.addString(b.dep_prefix), | |
| 177 | .root_path = try wc.addString(try b.root.toString(arena)), | |
| 178 | }); | |
| 179 | } | |
| 180 | return gop.value_ptr.*; | |
| 181 | } | |
| 182 | ||
| 183 | fn addOptionalLazyPathEnum(s: *Serialize, lp: ?std.Build.LazyPath) !Configuration.LazyPath.OptionalIndex { | |
| 184 | const wc = s.wc; | |
| 185 | return @enumFromInt(switch (lp orelse return .none) { | |
| 186 | .src_path => |src_path| i: { | |
| 187 | const sub_path = try wc.addString(src_path.sub_path); | |
| 188 | break :i try wc.addExtraErased(Configuration.LazyPath.SourcePath, .{ | |
| 189 | .owner = try s.builderToPackage(src_path.owner), | |
| 190 | .sub_path = sub_path, | |
| 191 | }); | |
| 192 | }, | |
| 193 | .generated => |generated| i: { | |
| 194 | const sub_path = try wc.addString(generated.sub_path); | |
| 195 | break :i try wc.addExtraErased(Configuration.LazyPath.Generated, .{ | |
| 196 | .flags = .{ .up = @intCast(generated.up) }, | |
| 197 | .index = generated.index, | |
| 198 | .sub_path = sub_path, | |
| 199 | }); | |
| 200 | }, | |
| 201 | .cwd_relative => |cwd_relative_sub_path| i: { | |
| 202 | const sub_path = try wc.addString(cwd_relative_sub_path); | |
| 203 | break :i try wc.addExtraErased(Configuration.LazyPath.Relative, .{ | |
| 204 | .flags = .{ .base = .cwd }, | |
| 205 | .sub_path = sub_path, | |
| 206 | }); | |
| 207 | }, | |
| 208 | .relative => |relative| i: { | |
| 209 | break :i try wc.addExtraErased(Configuration.LazyPath.Relative, .{ | |
| 210 | .flags = .{ .base = relative.base }, | |
| 211 | .sub_path = try wc.addString(relative.sub_path), | |
| 212 | }); | |
| 213 | }, | |
| 214 | .dependency => |dependency| i: { | |
| 215 | const sub_path = try wc.addString(dependency.sub_path); | |
| 216 | break :i try wc.addExtraErased(Configuration.LazyPath.SourcePath, .{ | |
| 217 | .owner = try s.builderToPackage(dependency.dependency.builder), | |
| 218 | .sub_path = sub_path, | |
| 219 | }); | |
| 220 | }, | |
| 221 | }); | |
| 222 | } | |
| 223 | ||
| 224 | fn addOptionalLazyPath(s: *Serialize, lp: ?std.Build.LazyPath) !?Configuration.LazyPath.Index { | |
| 225 | return (try addOptionalLazyPathEnum(s, lp)).unwrap(); | |
| 226 | } | |
| 227 | ||
| 228 | fn addLazyPath(s: *Serialize, lp: std.Build.LazyPath) !Configuration.LazyPath.Index { | |
| 229 | return @enumFromInt(@intFromEnum(try addOptionalLazyPathEnum(s, lp))); | |
| 230 | } | |
| 231 | ||
| 232 | fn addOptionalSemVer(s: *Serialize, sem_ver: ?std.SemanticVersion) !?Configuration.String { | |
| 233 | return if (sem_ver) |sv| try s.wc.addSemVer(sv) else null; | |
| 234 | } | |
| 235 | ||
| 236 | fn addOptionalString(s: *Serialize, opt_slice: ?[]const u8) !?Configuration.String { | |
| 237 | return if (opt_slice) |slice| try s.wc.addString(slice) else null; | |
| 238 | } | |
| 239 | ||
| 240 | fn addSystemLib(s: *Serialize, sl: *const std.Build.Module.SystemLib) !Configuration.SystemLib.Index { | |
| 241 | const wc = s.wc; | |
| 242 | return try wc.addDeduped(Configuration.SystemLib, .{ | |
| 243 | .flags = .{ | |
| 244 | .needed = sl.needed, | |
| 245 | .weak = sl.weak, | |
| 246 | .use_pkg_config = sl.use_pkg_config, | |
| 247 | .preferred_link_mode = sl.preferred_link_mode, | |
| 248 | .search_strategy = sl.search_strategy, | |
| 249 | }, | |
| 250 | .name = try wc.addString(sl.name), | |
| 251 | }); | |
| 252 | } | |
| 253 | ||
| 254 | fn addCSourceFile(s: *Serialize, csf: *const std.Build.Module.CSourceFile) !Configuration.CSourceFile.Index { | |
| 255 | const wc = s.wc; | |
| 256 | const args = try initStringList(s, csf.flags); | |
| 257 | return try wc.addExtra(Configuration.CSourceFile, .{ | |
| 258 | .flags = .{ | |
| 259 | .args_len = @intCast(args.len), | |
| 260 | .lang = .init(csf.language), | |
| 261 | }, | |
| 262 | .file = try addLazyPath(s, csf.file), | |
| 263 | .args = .{ .slice = args }, | |
| 264 | }); | |
| 265 | } | |
| 266 | ||
| 267 | fn addCSourceFiles(s: *Serialize, csf: *const std.Build.Module.CSourceFiles) !Configuration.CSourceFiles.Index { | |
| 268 | const wc = s.wc; | |
| 269 | const sub_paths = try initStringList(s, csf.files); | |
| 270 | const args = try initStringList(s, csf.flags); | |
| 271 | return try wc.addExtra(Configuration.CSourceFiles, .{ | |
| 272 | .flags = .{ | |
| 273 | .args_len = @intCast(args.len), | |
| 274 | .lang = .init(csf.language), | |
| 275 | }, | |
| 276 | .root = try addLazyPath(s, csf.root), | |
| 277 | .sub_paths = .{ .slice = sub_paths }, | |
| 278 | .args = .{ .slice = args }, | |
| 279 | }); | |
| 280 | } | |
| 281 | ||
| 282 | fn addRcSourceFile(s: *Serialize, rsf: *const std.Build.Module.RcSourceFile) !Configuration.RcSourceFile.Index { | |
| 283 | const wc = s.wc; | |
| 284 | const include_paths = try initLazyPathList(s, rsf.include_paths); | |
| 285 | const args = try initStringList(s, rsf.flags); | |
| 286 | return try wc.addExtra(Configuration.RcSourceFile, .{ | |
| 287 | .flags = .{ | |
| 288 | .args_len = @intCast(args.len), | |
| 289 | .include_paths = include_paths.len != 0, | |
| 290 | }, | |
| 291 | .file = try addLazyPath(s, rsf.file), | |
| 292 | .include_paths = .{ .slice = include_paths }, | |
| 293 | .args = .{ .slice = args }, | |
| 294 | }); | |
| 295 | } | |
| 296 | ||
| 297 | fn addEnvironMap(s: *Serialize, opt_map: ?*std.process.Environ.Map) !?Configuration.EnvironMap.Index { | |
| 298 | const wc = s.wc; | |
| 299 | const map = opt_map orelse return null; | |
| 300 | return try wc.addDeduped(Configuration.EnvironMap, .{ | |
| 301 | .keys = try wc.addStringList(map.array_hash_map.keys()), | |
| 302 | .values = try wc.addStringList(map.array_hash_map.values()), | |
| 303 | }); | |
| 304 | } | |
| 305 | ||
| 306 | fn initArgsList(s: *Serialize, args: []const Step.Run.Arg) ![]const Configuration.Step.Run.Arg.Index { | |
| 307 | const wc = s.wc; | |
| 308 | const result = try s.arena.alloc(Configuration.Step.Run.Arg.Index, args.len); | |
| 309 | for (result, args) |*dest, src| { | |
| 310 | dest.* = try wc.addExtra(Configuration.Step.Run.Arg, switch (src) { | |
| 311 | .artifact => |a| .{ | |
| 312 | .flags = .{ | |
| 313 | .tag = .artifact, | |
| 314 | .prefix = a.prefix.len != 0, | |
| 315 | .suffix = false, | |
| 316 | .basename = false, | |
| 317 | .path = false, | |
| 318 | .producer = true, | |
| 319 | .generated = false, | |
| 320 | .dep_file = false, | |
| 321 | }, | |
| 322 | .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null }, | |
| 323 | .suffix = .{ .value = null }, | |
| 324 | .basename = .{ .value = null }, | |
| 325 | .path = .{ .value = null }, | |
| 326 | .producer = .{ .value = stepIndex(s, &a.artifact.step) }, | |
| 327 | .generated = .{ .value = null }, | |
| 328 | }, | |
| 329 | .lazy_path => |a| .{ | |
| 330 | .flags = .{ | |
| 331 | .tag = .path_file, | |
| 332 | .prefix = a.prefix.len != 0, | |
| 333 | .suffix = false, | |
| 334 | .basename = false, | |
| 335 | .path = true, | |
| 336 | .producer = false, | |
| 337 | .generated = false, | |
| 338 | .dep_file = false, | |
| 339 | }, | |
| 340 | .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null }, | |
| 341 | .suffix = .{ .value = null }, | |
| 342 | .basename = .{ .value = null }, | |
| 343 | .path = .{ .value = try addLazyPath(s, a.lazy_path) }, | |
| 344 | .producer = .{ .value = null }, | |
| 345 | .generated = .{ .value = null }, | |
| 346 | }, | |
| 347 | .decorated_directory => |a| .{ | |
| 348 | .flags = .{ | |
| 349 | .tag = .path_directory, | |
| 350 | .prefix = a.prefix.len != 0, | |
| 351 | .suffix = a.suffix.len != 0, | |
| 352 | .basename = false, | |
| 353 | .path = true, | |
| 354 | .producer = false, | |
| 355 | .generated = false, | |
| 356 | .dep_file = false, | |
| 357 | }, | |
| 358 | .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null }, | |
| 359 | .suffix = .{ .value = if (a.suffix.len != 0) try wc.addString(a.suffix) else null }, | |
| 360 | .basename = .{ .value = null }, | |
| 361 | .path = .{ .value = try addLazyPath(s, a.lazy_path) }, | |
| 362 | .producer = .{ .value = null }, | |
| 363 | .generated = .{ .value = null }, | |
| 364 | }, | |
| 365 | .file_content => |a| .{ | |
| 366 | .flags = .{ | |
| 367 | .tag = .file_content, | |
| 368 | .prefix = a.prefix.len != 0, | |
| 369 | .suffix = false, | |
| 370 | .basename = false, | |
| 371 | .path = true, | |
| 372 | .producer = false, | |
| 373 | .generated = false, | |
| 374 | .dep_file = false, | |
| 375 | }, | |
| 376 | .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null }, | |
| 377 | .suffix = .{ .value = null }, | |
| 378 | .basename = .{ .value = null }, | |
| 379 | .path = .{ .value = try addLazyPath(s, a.lazy_path) }, | |
| 380 | .producer = .{ .value = null }, | |
| 381 | .generated = .{ .value = null }, | |
| 382 | }, | |
| 383 | .bytes => |a| .{ | |
| 384 | .flags = .{ | |
| 385 | .tag = .string, | |
| 386 | .prefix = true, | |
| 387 | .suffix = false, | |
| 388 | .basename = false, | |
| 389 | .path = false, | |
| 390 | .producer = false, | |
| 391 | .generated = false, | |
| 392 | .dep_file = false, | |
| 393 | }, | |
| 394 | .prefix = .{ .value = try wc.addString(a) }, | |
| 395 | .suffix = .{ .value = null }, | |
| 396 | .basename = .{ .value = null }, | |
| 397 | .path = .{ .value = null }, | |
| 398 | .producer = .{ .value = null }, | |
| 399 | .generated = .{ .value = null }, | |
| 400 | }, | |
| 401 | .output_file, .output_file_dep => |a, tag| .{ | |
| 402 | .flags = .{ | |
| 403 | .tag = .output_file, | |
| 404 | .prefix = a.prefix.len != 0, | |
| 405 | .suffix = false, | |
| 406 | .basename = a.basename.len != 0, | |
| 407 | .path = false, | |
| 408 | .producer = false, | |
| 409 | .generated = true, | |
| 410 | .dep_file = tag == .output_file_dep, | |
| 411 | }, | |
| 412 | .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null }, | |
| 413 | .suffix = .{ .value = null }, | |
| 414 | .basename = .{ .value = if (a.basename.len != 0) try wc.addString(a.basename) else null }, | |
| 415 | .path = .{ .value = null }, | |
| 416 | .producer = .{ .value = null }, | |
| 417 | .generated = .{ .value = a.generated_file }, | |
| 418 | }, | |
| 419 | .output_directory => |a| .{ | |
| 420 | .flags = .{ | |
| 421 | .tag = .output_directory, | |
| 422 | .prefix = a.prefix.len != 0, | |
| 423 | .suffix = false, | |
| 424 | .basename = a.basename.len != 0, | |
| 425 | .path = false, | |
| 426 | .producer = false, | |
| 427 | .generated = true, | |
| 428 | .dep_file = false, | |
| 429 | }, | |
| 430 | .prefix = .{ .value = if (a.prefix.len != 0) try wc.addString(a.prefix) else null }, | |
| 431 | .suffix = .{ .value = null }, | |
| 432 | .basename = .{ .value = if (a.basename.len != 0) try wc.addString(a.basename) else null }, | |
| 433 | .path = .{ .value = null }, | |
| 434 | .producer = .{ .value = null }, | |
| 435 | .generated = .{ .value = a.generated_file }, | |
| 436 | }, | |
| 437 | .passthru => .{ | |
| 438 | .flags = .{ | |
| 439 | .tag = .passthru, | |
| 440 | .prefix = false, | |
| 441 | .suffix = false, | |
| 442 | .basename = false, | |
| 443 | .path = false, | |
| 444 | .producer = false, | |
| 445 | .generated = false, | |
| 446 | .dep_file = false, | |
| 447 | }, | |
| 448 | .prefix = .{ .value = null }, | |
| 449 | .suffix = .{ .value = null }, | |
| 450 | .basename = .{ .value = null }, | |
| 451 | .path = .{ .value = null }, | |
| 452 | .producer = .{ .value = null }, | |
| 453 | .generated = .{ .value = null }, | |
| 454 | }, | |
| 455 | }); | |
| 456 | } | |
| 457 | return result; | |
| 458 | } | |
| 459 | ||
| 460 | fn initIncludeDirList( | |
| 461 | s: *Serialize, | |
| 462 | list: []const std.Build.Module.IncludeDir, | |
| 463 | ) ![]const Configuration.Module.IncludeDir { | |
| 464 | const result = try s.arena.alloc(Configuration.Module.IncludeDir, list.len); | |
| 465 | for (result, list) |*dest, src| dest.* = switch (src) { | |
| 466 | .path => |lp| .{ .path = try addLazyPath(s, lp) }, | |
| 467 | .path_system => |lp| .{ .path_system = try addLazyPath(s, lp) }, | |
| 468 | .path_after => |lp| .{ .path_after = try addLazyPath(s, lp) }, | |
| 469 | .framework_path => |lp| .{ .framework_path = try addLazyPath(s, lp) }, | |
| 470 | .framework_path_system => |lp| .{ .framework_path_system = try addLazyPath(s, lp) }, | |
| 471 | .embed_path => |lp| .{ .embed_path = try addLazyPath(s, lp) }, | |
| 472 | .other_step => |cs| .{ .path = try addLazyPath(s, cs.installed_headers_include_tree.?.getDirectory()) }, | |
| 473 | .config_header_step => |chs| .{ .config_header_step = stepIndex(s, &chs.step) }, | |
| 474 | }; | |
| 475 | return result; | |
| 476 | } | |
| 477 | ||
| 478 | fn initLazyPathList(s: *Serialize, list: []const std.Build.LazyPath) ![]const Configuration.LazyPath.Index { | |
| 479 | const result = try s.arena.alloc(Configuration.LazyPath.Index, list.len); | |
| 480 | for (result, list) |*dest, src| dest.* = try addLazyPath(s, src); | |
| 481 | return result; | |
| 482 | } | |
| 483 | ||
| 484 | fn initStringList(s: *Serialize, list: []const []const u8) ![]const Configuration.String { | |
| 485 | const wc = s.wc; | |
| 486 | const result = try s.arena.alloc(Configuration.String, list.len); | |
| 487 | for (result, list) |*dest, src| dest.* = try wc.addString(src); | |
| 488 | return result; | |
| 489 | } | |
| 490 | ||
| 491 | fn initCopyList(s: *Serialize, list: []const Step.WriteFile.Copy) ![]const Configuration.Step.WriteFile.Copy { | |
| 492 | const result = try s.arena.alloc(Configuration.Step.WriteFile.Copy, list.len); | |
| 493 | for (result, list) |*dest, src| dest.* = .{ | |
| 494 | .sub_path = src.sub_path, | |
| 495 | .src_file = try s.addLazyPath(src.src_file), | |
| 496 | }; | |
| 497 | return result; | |
| 498 | } | |
| 499 | ||
| 500 | fn initOptionalStringList(s: *Serialize, list: []const ?[]const u8) ![]const Configuration.OptionalString { | |
| 501 | const wc = s.wc; | |
| 502 | const result = try s.arena.alloc(Configuration.OptionalString, list.len); | |
| 503 | for (result, list) |*dest, src| dest.* = try wc.addOptionalString(src); | |
| 504 | return result; | |
| 505 | } | |
| 506 | ||
| 507 | fn addModule(s: *Serialize, m: *std.Build.Module) !Configuration.Module.Index { | |
| 508 | if (s.module_map.get(m)) |index| return index; | |
| 509 | ||
| 510 | const wc = s.wc; | |
| 511 | const arena = s.arena; | |
| 512 | ||
| 513 | const rpaths = try arena.alloc(Configuration.Module.RPath, m.rpaths.items.len); | |
| 514 | for (rpaths, m.rpaths.items) |*dest, src| dest.* = switch (src) { | |
| 515 | .lazy_path => |lp| .{ .lazy_path = try addLazyPath(s, lp) }, | |
| 516 | .special => |slice| .{ .special = try wc.addString(slice) }, | |
| 517 | }; | |
| 518 | ||
| 519 | const link_objects = try arena.alloc(Configuration.Module.LinkObject, m.link_objects.items.len); | |
| 520 | for (link_objects, m.link_objects.items) |*dest, *src| dest.* = switch (src.*) { | |
| 521 | .static_path => |lp| .{ .static_path = try addLazyPath(s, lp) }, | |
| 522 | .other_step => |cs| .{ .other_step = stepIndex(s, &cs.step) }, | |
| 523 | .system_lib => |*sl| .{ .system_lib = try addSystemLib(s, sl) }, | |
| 524 | .assembly_file => |lp| .{ .assembly_file = try addLazyPath(s, lp) }, | |
| 525 | .c_source_file => |csf| .{ .c_source_file = try addCSourceFile(s, csf) }, | |
| 526 | .c_source_files => |csf| .{ .c_source_files = try addCSourceFiles(s, csf) }, | |
| 527 | .win32_resource_file => |wrf| .{ .win32_resource_file = try addRcSourceFile(s, wrf) }, | |
| 528 | }; | |
| 529 | ||
| 530 | const frameworks = try arena.alloc(Configuration.Module.Framework, m.frameworks.entries.len); | |
| 531 | for (frameworks, m.frameworks.keys(), m.frameworks.values()) |*dest, name, options| dest.* = .{ | |
| 532 | .flags = .{ | |
| 533 | .needed = options.needed, | |
| 534 | .weak = options.weak, | |
| 535 | }, | |
| 536 | .name = try wc.addString(name), | |
| 537 | }; | |
| 538 | ||
| 539 | const lib_paths = try initLazyPathList(s, m.lib_paths.items); | |
| 540 | const c_macros = try initStringList(s, m.c_macros.items); | |
| 541 | const export_symbol_names = try initStringList(s, m.export_symbol_names); | |
| 542 | ||
| 543 | const module_index: Configuration.Module.Index = try wc.addExtra(Configuration.Module, .{ | |
| 544 | .flags = .{ | |
| 545 | .optimize = .init(m.optimize), | |
| 546 | .strip = .init(m.strip), | |
| 547 | .unwind_tables = .init(m.unwind_tables), | |
| 548 | .dwarf_format = .init(m.dwarf_format), | |
| 549 | .single_threaded = .init(m.single_threaded), | |
| 550 | .stack_protector = .init(m.stack_protector), | |
| 551 | .stack_check = .init(m.stack_check), | |
| 552 | .sanitize_c = .init(m.sanitize_c), | |
| 553 | .sanitize_thread = .init(m.sanitize_thread), | |
| 554 | .fuzz = .init(m.fuzz), | |
| 555 | .code_model = m.code_model, | |
| 556 | .c_macros = c_macros.len != 0, | |
| 557 | .include_dirs = m.include_dirs.items.len != 0, | |
| 558 | .lib_paths = lib_paths.len != 0, | |
| 559 | .rpaths = rpaths.len != 0, | |
| 560 | .frameworks = frameworks.len != 0, | |
| 561 | .link_objects = link_objects.len != 0, | |
| 562 | .export_symbol_names = export_symbol_names.len != 0, | |
| 563 | }, | |
| 564 | .flags2 = .{ | |
| 565 | .valgrind = .init(m.valgrind), | |
| 566 | .pic = .init(m.pic), | |
| 567 | .red_zone = .init(m.red_zone), | |
| 568 | .omit_frame_pointer = .init(m.omit_frame_pointer), | |
| 569 | .error_tracing = .init(m.error_tracing), | |
| 570 | .link_libc = .init(m.link_libc), | |
| 571 | .link_libcpp = .init(m.link_libcpp), | |
| 572 | .no_builtin = .init(m.no_builtin), | |
| 573 | }, | |
| 574 | .owner = try s.builderToPackage(m.owner), | |
| 575 | .root_source_file = try s.addOptionalLazyPathEnum(m.root_source_file), | |
| 576 | .import_table = .invalid, | |
| 577 | .resolved_target = try addOptionalResolvedTarget(wc, m.resolved_target), | |
| 578 | .c_macros = .{ .slice = c_macros }, | |
| 579 | .lib_paths = .{ .slice = lib_paths }, | |
| 580 | .export_symbol_names = .{ .slice = export_symbol_names }, | |
| 581 | .include_dirs = .init(try s.initIncludeDirList(m.include_dirs.items)), | |
| 582 | .rpaths = .init(rpaths), | |
| 583 | .link_objects = .init(link_objects), | |
| 584 | .frameworks = .{ .slice = frameworks }, | |
| 585 | }); | |
| 586 | ||
| 587 | // The import table is the only place that modules can form dependency | |
| 588 | // loops. Therefore, we populate the module indexes only after adding | |
| 589 | // the module to module_map. | |
| 590 | try s.module_map.putNoClobber(arena, m, module_index); | |
| 591 | ||
| 592 | var imports = try std.MultiArrayList(Configuration.ImportTable.Import).initCapacity(arena, m.import_table.entries.len); | |
| 593 | imports.len = m.import_table.entries.len; | |
| 594 | for ( | |
| 595 | imports.items(.name), | |
| 596 | imports.items(.module), | |
| 597 | m.import_table.keys(), | |
| 598 | m.import_table.values(), | |
| 599 | ) |*dest_name, *dest_module, src_name, src_module| { | |
| 600 | dest_name.* = try wc.addString(src_name); | |
| 601 | dest_module.* = try addModule(s, src_module); | |
| 602 | } | |
| 603 | ||
| 604 | comptime assert(std.mem.eql(u8, @typeInfo(Configuration.Module).@"struct".fields[2].name, "import_table")); | |
| 605 | comptime assert(@typeInfo(Configuration.Module).@"struct".fields[2].type == Configuration.ImportTable.Index); | |
| 606 | assert(wc.extra.items[@intFromEnum(module_index) + 2] == @intFromEnum(Configuration.ImportTable.Index.invalid)); | |
| 607 | const import_table_index = try wc.addDeduped(Configuration.ImportTable, .{ | |
| 608 | .imports = .{ .mal = imports }, | |
| 609 | }); | |
| 610 | wc.extra.items[@intFromEnum(module_index) + 2] = @intFromEnum(import_table_index); | |
| 611 | ||
| 612 | return module_index; | |
| 613 | } | |
| 614 | ||
| 615 | fn stepIndex(s: *const Serialize, step: *Step) Configuration.Step.Index { | |
| 616 | return @enumFromInt(s.step_map.getIndex(step).?); | |
| 617 | } | |
| 618 | }; | |
| 619 | ||
| 620 | fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { | |
| 621 | const graph = b.graph; | |
| 622 | const arena = graph.arena; | |
| 623 | const gpa = wc.gpa; | |
| 624 | ||
| 625 | var s: Serialize = .{ .wc = wc, .arena = arena }; | |
| 626 | ||
| 627 | // Starting from all top-level steps in `b`, traverse the entire step graph | |
| 628 | // and add all step dependencies implied by module graphs. | |
| 629 | const top_level_steps = b.top_level_steps.values(); | |
| 630 | try s.step_map.ensureUnusedCapacity(arena, top_level_steps.len); | |
| 631 | for (top_level_steps) |tls| { | |
| 632 | s.step_map.putAssumeCapacityNoClobber(&tls.step, {}); | |
| 633 | } | |
| 634 | { | |
| 635 | while (wc.steps.items.len < s.step_map.count()) { | |
| 636 | const step = s.step_map.keys()[wc.steps.items.len]; | |
| 637 | ||
| 638 | // Set up any implied dependencies for this step. It's important that we do this first, so | |
| 639 | // that the loop below discovers steps implied by the module graph. | |
| 640 | try createModuleDependenciesForStep(step); | |
| 641 | ||
| 642 | try s.step_map.ensureUnusedCapacity(arena, step.dependencies.items.len); | |
| 643 | for (step.dependencies.items) |other_step| { | |
| 644 | s.step_map.putAssumeCapacity(other_step, {}); | |
| 645 | } | |
| 646 | ||
| 647 | // Add and then de-duplicate dependencies. | |
| 648 | const dep_steps = try arena.alloc(Configuration.Step.Index, step.dependencies.items.len); | |
| 649 | for (dep_steps, step.dependencies.items) |*dest, src| | |
| 650 | dest.* = @enumFromInt(s.step_map.getIndex(src).?); | |
| 651 | ||
| 652 | const deps: Configuration.Deps.Index = try wc.addDeduped(Configuration.Deps, .{ | |
| 653 | .steps = .{ .slice = dep_steps }, | |
| 654 | }); | |
| 655 | ||
| 656 | try wc.steps.ensureTotalCapacity(gpa, s.step_map.entries.capacity); | |
| 657 | wc.steps.appendAssumeCapacity(.{ | |
| 658 | .name = try wc.addString(step.name), | |
| 659 | .owner = try s.builderToPackage(step.owner), | |
| 660 | .deps = deps, | |
| 661 | .max_rss = .fromBytes(step.max_rss), | |
| 662 | .extended = @enumFromInt(switch (step.tag) { | |
| 663 | .top_level => e: { | |
| 664 | const top_level: *Step.TopLevel = @fieldParentPtr("step", step); | |
| 665 | break :e try wc.addExtraErased(Configuration.Step.TopLevel, .{ | |
| 666 | .description = try wc.addString(top_level.description), | |
| 667 | }); | |
| 668 | }, | |
| 669 | .compile => e: { | |
| 670 | const c: *Step.Compile = @fieldParentPtr("step", step); | |
| 671 | const exec_cmd_args: []const ?[]const u8 = c.exec_cmd_args orelse &.{}; | |
| 672 | const installed_headers: []u32 = try arena.alloc(u32, c.installed_headers.items.len); | |
| 673 | for (installed_headers, c.installed_headers.items) |*dst, src| switch (src) { | |
| 674 | .file => |file| { | |
| 675 | dst.* = try wc.addExtraErased(Configuration.Step.Compile.InstalledHeader.File, .{ | |
| 676 | .source = try s.addLazyPath(file.source), | |
| 677 | .dest_sub_path = try wc.addString(file.dest_rel_path), | |
| 678 | }); | |
| 679 | }, | |
| 680 | .directory => |directory| { | |
| 681 | const include_extensions = directory.options.include_extensions orelse &.{}; | |
| 682 | dst.* = try wc.addExtraErased(Configuration.Step.Compile.InstalledHeader.Directory, .{ | |
| 683 | .flags = .{ | |
| 684 | .include_extensions = include_extensions.len != 0, | |
| 685 | .exclude_extensions = directory.options.exclude_extensions.len != 0, | |
| 686 | }, | |
| 687 | .source = try s.addLazyPath(directory.source), | |
| 688 | .dest_sub_path = try wc.addString(directory.dest_rel_path), | |
| 689 | .exclude_extensions = .{ .slice = try s.initStringList(directory.options.exclude_extensions) }, | |
| 690 | .include_extensions = .{ .slice = try s.initStringList(include_extensions) }, | |
| 691 | }); | |
| 692 | }, | |
| 693 | }; | |
| 694 | ||
| 695 | break :e try wc.addExtraErased(Configuration.Step.Compile, .{ | |
| 696 | .flags = .{ | |
| 697 | .filters_len = c.filters.len != 0, | |
| 698 | .exec_cmd_args_len = exec_cmd_args.len != 0, | |
| 699 | .installed_headers_len = installed_headers.len != 0, | |
| 700 | .force_undefined_symbols_len = c.force_undefined_symbols.entries.len != 0, | |
| 701 | ||
| 702 | .verbose_link = c.verbose_link, | |
| 703 | .verbose_cc = c.verbose_cc, | |
| 704 | .rdynamic = c.rdynamic, | |
| 705 | .import_memory = c.import_memory, | |
| 706 | .export_memory = c.export_memory, | |
| 707 | .import_symbols = c.import_symbols, | |
| 708 | .import_table = c.import_table, | |
| 709 | .export_table = c.export_table, | |
| 710 | .shared_memory = c.shared_memory, | |
| 711 | .link_eh_frame_hdr = c.link_eh_frame_hdr, | |
| 712 | .link_emit_relocs = c.link_emit_relocs, | |
| 713 | .link_function_sections = c.link_function_sections, | |
| 714 | .link_data_sections = c.link_data_sections, | |
| 715 | .linker_dynamicbase = c.linker_dynamicbase, | |
| 716 | .link_z_notext = c.link_z_notext, | |
| 717 | .link_z_relro = c.link_z_relro, | |
| 718 | .link_z_lazy = c.link_z_lazy, | |
| 719 | .link_z_defs = c.link_z_defs, | |
| 720 | .headerpad_max_install_names = c.headerpad_max_install_names, | |
| 721 | .dead_strip_dylibs = c.dead_strip_dylibs, | |
| 722 | .force_load_objc = c.force_load_objc, | |
| 723 | .discard_local_symbols = c.discard_local_symbols, | |
| 724 | .mingw_unicode_entry_point = c.mingw_unicode_entry_point, | |
| 725 | }, | |
| 726 | .flags2 = .{ | |
| 727 | .pie = .init(c.pie), | |
| 728 | .formatted_panics = .init(c.formatted_panics), | |
| 729 | .bundle_compiler_rt = .init(c.bundle_compiler_rt), | |
| 730 | .bundle_ubsan_rt = .init(c.bundle_ubsan_rt), | |
| 731 | .each_lib_rpath = .init(c.each_lib_rpath), | |
| 732 | .link_gc_sections = .init(c.link_gc_sections), | |
| 733 | .linker_allow_shlib_undefined = .init(c.linker_allow_shlib_undefined), | |
| 734 | .linker_allow_undefined_version = .init(c.linker_allow_undefined_version), | |
| 735 | .linker_enable_new_dtags = .init(c.linker_enable_new_dtags), | |
| 736 | .dll_export_fns = .init(c.dll_export_fns), | |
| 737 | .use_llvm = .init(c.use_llvm), | |
| 738 | .use_lld = .init(c.use_lld), | |
| 739 | .use_new_linker = .init(c.use_new_linker), | |
| 740 | .allow_so_scripts = .init(c.allow_so_scripts), | |
| 741 | .sanitize_coverage_trace_pc_guard = .init(c.sanitize_coverage_trace_pc_guard), | |
| 742 | .linkage = .init(c.linkage), | |
| 743 | }, | |
| 744 | .flags3 = .{ | |
| 745 | .is_linking_libc = c.is_linking_libc, | |
| 746 | .is_linking_libcpp = c.is_linking_libcpp, | |
| 747 | .version = c.version != null, | |
| 748 | .compress_debug_sections = c.compress_debug_sections, | |
| 749 | .initial_memory = c.initial_memory != null, | |
| 750 | .max_memory = c.max_memory != null, | |
| 751 | .kind = c.kind, | |
| 752 | .global_base = c.global_base != null, | |
| 753 | .test_runner = if (c.test_runner) |tr| switch (tr.mode) { | |
| 754 | .simple => .simple, | |
| 755 | .server => .server, | |
| 756 | } else .default, | |
| 757 | .wasi_exec_model = .init(c.wasi_exec_model), | |
| 758 | .win32_manifest = c.win32_manifest != null, | |
| 759 | .win32_module_definition = c.win32_module_definition != null, | |
| 760 | .zig_lib_dir = c.zig_lib_dir != null, | |
| 761 | .rc_includes = c.rc_includes, | |
| 762 | .image_base = c.image_base != null, | |
| 763 | .build_id = .init(c.build_id), | |
| 764 | .entry = switch (c.entry) { | |
| 765 | .default => .default, | |
| 766 | .disabled => .disabled, | |
| 767 | .enabled => .enabled, | |
| 768 | .symbol_name => .symbol_name, | |
| 769 | }, | |
| 770 | .lto = .init(c.lto), | |
| 771 | .subsystem = .init(c.subsystem), | |
| 772 | }, | |
| 773 | .flags4 = .{ | |
| 774 | .libc_file = c.libc_file != null, | |
| 775 | .link_z_common_page_size = c.link_z_common_page_size != null, | |
| 776 | .link_z_max_page_size = c.link_z_max_page_size != null, | |
| 777 | .pagezero_size = c.pagezero_size != null, | |
| 778 | .stack_size = c.stack_size != null, | |
| 779 | .headerpad_size = c.headerpad_size != null, | |
| 780 | .error_limit = c.error_limit != null, | |
| 781 | .install_name = c.install_name != null, | |
| 782 | .entitlements = c.entitlements != null, | |
| 783 | .expect_errors = if (c.expect_errors) |x| switch (x) { | |
| 784 | .contains => .contains, | |
| 785 | .exact => .exact, | |
| 786 | .starts_with => .starts_with, | |
| 787 | .stderr_contains => .stderr_contains, | |
| 788 | } else .none, | |
| 789 | .linker_script = c.linker_script != null, | |
| 790 | .version_script = c.version_script != null, | |
| 791 | .emit_directory = c.emit_directory != .none, | |
| 792 | .generated_docs = c.generated_docs != .none, | |
| 793 | .generated_asm = c.generated_asm != .none, | |
| 794 | .generated_bin = c.generated_bin != .none, | |
| 795 | .generated_pdb = c.generated_pdb != .none, | |
| 796 | .generated_implib = c.generated_implib != .none, | |
| 797 | .generated_llvm_bc = c.generated_llvm_bc != .none, | |
| 798 | .generated_llvm_ir = c.generated_llvm_ir != .none, | |
| 799 | .generated_h = c.generated_h != .none, | |
| 800 | }, | |
| 801 | .root_module = try s.addModule(c.root_module), | |
| 802 | .root_name = try wc.addString(c.name), | |
| 803 | .linker_script = .{ .value = try s.addOptionalLazyPath(c.linker_script) }, | |
| 804 | .version_script = .{ .value = try s.addOptionalLazyPath(c.version_script) }, | |
| 805 | .zig_lib_dir = .{ .value = try s.addOptionalLazyPath(c.zig_lib_dir) }, | |
| 806 | .libc_file = .{ .value = try s.addOptionalLazyPath(c.libc_file) }, | |
| 807 | .win32_manifest = .{ .value = try s.addOptionalLazyPath(c.win32_manifest) }, | |
| 808 | .win32_module_definition = .{ .value = try s.addOptionalLazyPath(c.win32_module_definition) }, | |
| 809 | .entitlements = .{ .value = try s.addOptionalLazyPath(c.entitlements) }, | |
| 810 | .version = .{ .value = try s.addOptionalSemVer(c.version) }, | |
| 811 | .install_name = .{ .value = try s.addOptionalString(c.install_name) }, | |
| 812 | .initial_memory = .{ .value = c.initial_memory }, | |
| 813 | .max_memory = .{ .value = c.max_memory }, | |
| 814 | .global_base = .{ .value = c.global_base }, | |
| 815 | .image_base = .{ .value = c.image_base }, | |
| 816 | .link_z_common_page_size = .{ .value = c.link_z_common_page_size }, | |
| 817 | .link_z_max_page_size = .{ .value = c.link_z_max_page_size }, | |
| 818 | .pagezero_size = .{ .value = c.pagezero_size }, | |
| 819 | .stack_size = .{ .value = c.stack_size }, | |
| 820 | .headerpad_size = .{ .value = c.headerpad_size }, | |
| 821 | .error_limit = .{ .value = c.error_limit }, | |
| 822 | .entry = .{ .value = switch (c.entry) { | |
| 823 | .symbol_name => |name| try wc.addString(name), | |
| 824 | .default, .disabled, .enabled => null, | |
| 825 | } }, | |
| 826 | .build_id = .{ .value = if (c.build_id) |id| switch (id) { | |
| 827 | .hexstring => |*hexstring| try wc.addString(hexstring.toSlice()), | |
| 828 | .none, .fast, .uuid, .sha1, .md5 => null, | |
| 829 | } else null }, | |
| 830 | .filters = .{ .slice = try s.initStringList(c.filters) }, | |
| 831 | .exec_cmd_args = .{ .slice = try s.initOptionalStringList(exec_cmd_args) }, | |
| 832 | .installed_headers = .initErased(installed_headers), | |
| 833 | .force_undefined_symbols = .{ .slice = try s.initStringList(c.force_undefined_symbols.keys()) }, | |
| 834 | .expect_errors = .{ .u = if (c.expect_errors) |x| switch (x) { | |
| 835 | .contains => |slice| .{ .contains = try wc.addString(slice) }, | |
| 836 | .exact => |exact| .{ .exact = .{ .slice = try s.initStringList(exact) } }, | |
| 837 | .starts_with => |slice| .{ .starts_with = try wc.addString(slice) }, | |
| 838 | .stderr_contains => |slice| .{ .stderr_contains = try wc.addString(slice) }, | |
| 839 | } else .none }, | |
| 840 | .test_runner = .{ .u = if (c.test_runner) |tr| switch (tr.mode) { | |
| 841 | .simple => .{ .simple = try s.addLazyPath(tr.path) }, | |
| 842 | .server => .{ .server = try s.addLazyPath(tr.path) }, | |
| 843 | } else .default }, | |
| 844 | ||
| 845 | .emit_directory = .{ .value = c.emit_directory.unwrap() }, | |
| 846 | .generated_docs = .{ .value = c.generated_docs.unwrap() }, | |
| 847 | .generated_asm = .{ .value = c.generated_asm.unwrap() }, | |
| 848 | .generated_bin = .{ .value = c.generated_bin.unwrap() }, | |
| 849 | .generated_pdb = .{ .value = c.generated_pdb.unwrap() }, | |
| 850 | .generated_implib = .{ .value = c.generated_implib.unwrap() }, | |
| 851 | .generated_llvm_bc = .{ .value = c.generated_llvm_bc.unwrap() }, | |
| 852 | .generated_llvm_ir = .{ .value = c.generated_llvm_ir.unwrap() }, | |
| 853 | .generated_h = .{ .value = c.generated_h.unwrap() }, | |
| 854 | }); | |
| 855 | }, | |
| 856 | .install_artifact => e: { | |
| 857 | const ia: *Step.InstallArtifact = @fieldParentPtr("step", step); | |
| 858 | break :e try wc.addExtraErased(Configuration.Step.InstallArtifact, .{ | |
| 859 | .flags = .{ | |
| 860 | .dylib_symlinks = ia.dylib_symlinks, | |
| 861 | .bin_dir = ia.dest_dir != null, | |
| 862 | .implib_dir = ia.implib_dir != null, | |
| 863 | .pdb_dir = ia.pdb_dir != null, | |
| 864 | .h_dir = ia.h_dir != null, | |
| 865 | .bin_sub_path = ia.dest_sub_path != null, | |
| 866 | }, | |
| 867 | .bin_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.dest_dir) }, | |
| 868 | .implib_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.implib_dir) }, | |
| 869 | .pdb_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.pdb_dir) }, | |
| 870 | .h_dir = .{ .value = try addInstallDirDefaultNull(wc, ia.h_dir) }, | |
| 871 | .bin_sub_path = .{ .value = try s.addOptionalString(ia.dest_sub_path) }, | |
| 872 | }); | |
| 873 | }, | |
| 874 | .install_file => e: { | |
| 875 | const sif: *Step.InstallFile = @fieldParentPtr("step", step); | |
| 876 | break :e try wc.addExtraErased(Configuration.Step.InstallFile, .{ | |
| 877 | .source = try s.addLazyPath(sif.source), | |
| 878 | .dest_dir = try addInstallDir(wc, sif.dir), | |
| 879 | .dest_sub_path = try wc.addString(sif.dest_rel_path), | |
| 880 | }); | |
| 881 | }, | |
| 882 | .install_dir => e: { | |
| 883 | const sid: *Step.InstallDir = @fieldParentPtr("step", step); | |
| 884 | const dest_sub_path: ?[]const u8 = if (sid.options.install_subdir.len != 0) | |
| 885 | sid.options.install_subdir | |
| 886 | else | |
| 887 | null; | |
| 888 | const include_extensions = sid.options.include_extensions orelse &.{}; | |
| 889 | break :e try wc.addExtraErased(Configuration.Step.InstallDir, .{ | |
| 890 | .flags = .{ | |
| 891 | .dest_sub_path = dest_sub_path != null, | |
| 892 | .exclude_extensions = sid.options.exclude_extensions.len != 0, | |
| 893 | .include_extensions = include_extensions.len != 0, | |
| 894 | .include_extensions_active = sid.options.include_extensions != null, | |
| 895 | .blank_extensions = sid.options.blank_extensions.len != 0, | |
| 896 | }, | |
| 897 | .source_dir = try s.addLazyPath(sid.options.source_dir), | |
| 898 | .dest_dir = try addInstallDir(wc, sid.options.install_dir), | |
| 899 | .dest_sub_path = .{ .value = try s.addOptionalString(dest_sub_path) }, | |
| 900 | .exclude_extensions = .{ .slice = try s.initStringList(sid.options.exclude_extensions) }, | |
| 901 | .include_extensions = .{ .slice = try s.initStringList(include_extensions) }, | |
| 902 | .blank_extensions = .{ .slice = try s.initStringList(sid.options.blank_extensions) }, | |
| 903 | }); | |
| 904 | }, | |
| 905 | .fail => e: { | |
| 906 | const sf: *Step.Fail = @fieldParentPtr("step", step); | |
| 907 | break :e try wc.addExtraErased(Configuration.Step.Fail, .{ | |
| 908 | .msg = sf.error_msg, | |
| 909 | }); | |
| 910 | }, | |
| 911 | .find_program => e: { | |
| 912 | const fp: *Step.FindProgram = @fieldParentPtr("step", step); | |
| 913 | break :e try wc.addExtraErased(Configuration.Step.FindProgram, .{ | |
| 914 | .names = fp.names, | |
| 915 | .found_path = fp.found_path, | |
| 916 | }); | |
| 917 | }, | |
| 918 | .fmt => e: { | |
| 919 | const sf: *Step.Fmt = @fieldParentPtr("step", step); | |
| 920 | break :e try wc.addExtraErased(Configuration.Step.Fmt, .{ | |
| 921 | .flags = .{ | |
| 922 | .paths = sf.paths.len != 0, | |
| 923 | .exclude_paths = sf.exclude_paths.len != 0, | |
| 924 | .check = sf.check, | |
| 925 | }, | |
| 926 | .paths = .{ .slice = try s.initLazyPathList(sf.paths) }, | |
| 927 | .exclude_paths = .{ .slice = try s.initLazyPathList(sf.exclude_paths) }, | |
| 928 | }); | |
| 929 | }, | |
| 930 | .translate_c => e: { | |
| 931 | const tc: *Step.TranslateC = @fieldParentPtr("step", step); | |
| 932 | ||
| 933 | const system_libs = try arena.alloc(Configuration.SystemLib.Index, tc.system_libs.items.len); | |
| 934 | for (system_libs, tc.system_libs.items) |*dest, *src| dest.* = try s.addSystemLib(src); | |
| 935 | ||
| 936 | break :e try wc.addExtraErased(Configuration.Step.TranslateC, .{ | |
| 937 | .flags = .{ | |
| 938 | .include_dirs = tc.include_dirs.items.len != 0, | |
| 939 | .system_libs = system_libs.len != 0, | |
| 940 | .c_macros = tc.c_macros.items.len != 0, | |
| 941 | .link_libc = tc.link_libc, | |
| 942 | .optimize = .init(tc.optimize), | |
| 943 | }, | |
| 944 | .src_path = try s.addLazyPath(tc.source), | |
| 945 | .output_file = tc.output_file, | |
| 946 | .include_dirs = .init(try s.initIncludeDirList(tc.include_dirs.items)), | |
| 947 | .system_libs = .{ .slice = system_libs }, | |
| 948 | .c_macros = .{ .slice = tc.c_macros.items }, | |
| 949 | .target = try addOptionalResolvedTarget(wc, tc.target), | |
| 950 | }); | |
| 951 | }, | |
| 952 | .write_file => e: { | |
| 953 | const wf: *Step.WriteFile = @fieldParentPtr("step", step); | |
| 954 | ||
| 955 | const directories = try arena.alloc( | |
| 956 | Configuration.Step.WriteFile.Directory, | |
| 957 | wf.directories.items.len, | |
| 958 | ); | |
| 959 | for (directories, wf.directories.items) |*dest, src| dest.* = .{ | |
| 960 | .sub_path = src.sub_path, | |
| 961 | .src_path = try s.addLazyPath(src.src_path), | |
| 962 | .exclude_extensions = src.exclude_extensions, | |
| 963 | .include_extensions = src.include_extensions, | |
| 964 | }; | |
| 965 | ||
| 966 | break :e try wc.addExtraErased(Configuration.Step.WriteFile, .{ | |
| 967 | .flags = .{ | |
| 968 | .embeds = wf.embeds.items.len != 0, | |
| 969 | .copies = wf.copies.items.len != 0, | |
| 970 | .directories = directories.len != 0, | |
| 971 | .mode = switch (wf.mode) { | |
| 972 | .whole_cached => .whole_cached, | |
| 973 | .tmp => .tmp, | |
| 974 | .mutate => .mutate, | |
| 975 | }, | |
| 976 | }, | |
| 977 | .generated_directory = wf.generated_directory, | |
| 978 | .embeds = .{ .slice = wf.embeds.items }, | |
| 979 | .copies = .{ .slice = try s.initCopyList(wf.copies.items) }, | |
| 980 | .directories = .{ .slice = directories }, | |
| 981 | .mutate_path = .{ .value = switch (wf.mode) { | |
| 982 | .mutate => |lp| try s.addLazyPath(lp), | |
| 983 | .whole_cached, .tmp => null, | |
| 984 | } }, | |
| 985 | }); | |
| 986 | }, | |
| 987 | .update_source_files => e: { | |
| 988 | const usf: *Step.UpdateSourceFiles = @fieldParentPtr("step", step); | |
| 989 | break :e try wc.addExtraErased(Configuration.Step.UpdateSourceFiles, .{ | |
| 990 | .flags = .{ | |
| 991 | .embeds = usf.embeds.items.len != 0, | |
| 992 | .copies = usf.copies.items.len != 0, | |
| 993 | }, | |
| 994 | .embeds = .{ .slice = usf.embeds.items }, | |
| 995 | .copies = .{ .slice = try s.initCopyList(usf.copies.items) }, | |
| 996 | }); | |
| 997 | }, | |
| 998 | .run => e: { | |
| 999 | const run: *Step.Run = @fieldParentPtr("step", step); | |
| 1000 | var expect_stderr_exact: ?Configuration.Bytes = null; | |
| 1001 | var expect_stdout_exact: ?Configuration.Bytes = null; | |
| 1002 | var expect_stderr_match: std.ArrayList(Configuration.Bytes) = .empty; | |
| 1003 | var expect_stdout_match: std.ArrayList(Configuration.Bytes) = .empty; | |
| 1004 | var expect_term: ?struct { | |
| 1005 | status: Configuration.Step.Run.ExpectTermStatus, | |
| 1006 | value: u32, | |
| 1007 | } = null; | |
| 1008 | switch (run.stdio) { | |
| 1009 | .check => |checks| for (checks.items) |check| switch (check) { | |
| 1010 | .expect_stderr_exact => |bytes| expect_stderr_exact = try wc.addBytes(bytes), | |
| 1011 | .expect_stdout_exact => |bytes| expect_stdout_exact = try wc.addBytes(bytes), | |
| 1012 | .expect_stderr_match => |bytes| { | |
| 1013 | try expect_stderr_match.append(arena, try wc.addBytes(bytes)); | |
| 1014 | }, | |
| 1015 | .expect_stdout_match => |bytes| { | |
| 1016 | try expect_stdout_match.append(arena, try wc.addBytes(bytes)); | |
| 1017 | }, | |
| 1018 | .expect_term => |t| expect_term = switch (t) { | |
| 1019 | .exited => |x| .{ .status = .exited, .value = x }, | |
| 1020 | .signal => |x| .{ .status = .signal, .value = @intFromEnum(x) }, | |
| 1021 | .stopped => |x| .{ .status = .stopped, .value = @intFromEnum(x) }, | |
| 1022 | .unknown => |x| .{ .status = .unknown, .value = x }, | |
| 1023 | }, | |
| 1024 | }, | |
| 1025 | else => {}, | |
| 1026 | } | |
| 1027 | ||
| 1028 | break :e try wc.addExtraErased(Configuration.Step.Run, .{ | |
| 1029 | .flags = .{ | |
| 1030 | .disable_zig_progress = run.disable_zig_progress, | |
| 1031 | .skip_foreign_checks = run.skip_foreign_checks, | |
| 1032 | .failing_to_execute_foreign_is_an_error = run.failing_to_execute_foreign_is_an_error, | |
| 1033 | .has_side_effects = run.has_side_effects, | |
| 1034 | .test_runner_mode = run.test_runner_mode, | |
| 1035 | .color = run.color, | |
| 1036 | .stdio = switch (run.stdio) { | |
| 1037 | .infer_from_args => .infer_from_args, | |
| 1038 | .inherit => .inherit, | |
| 1039 | .check => .check, | |
| 1040 | .zig_test => .zig_test, | |
| 1041 | }, | |
| 1042 | .stdin = switch (run.stdin) { | |
| 1043 | .none => .none, | |
| 1044 | .bytes => .bytes, | |
| 1045 | .lazy_path => .lazy_path, | |
| 1046 | }, | |
| 1047 | .stdout_trim_whitespace = if (run.captured_stdout) |cs| cs.trim_whitespace else .none, | |
| 1048 | .stderr_trim_whitespace = if (run.captured_stderr) |cs| cs.trim_whitespace else .none, | |
| 1049 | .stdio_limit = run.stdio_limit != .unlimited, | |
| 1050 | .producer = run.producer != null, | |
| 1051 | .cwd = run.cwd != null, | |
| 1052 | .captured_stdout = run.captured_stdout != null, | |
| 1053 | .captured_stderr = run.captured_stderr != null, | |
| 1054 | .environ_map = run.environ_map != null, | |
| 1055 | }, | |
| 1056 | .flags2 = .{ | |
| 1057 | .expect_stderr_exact = expect_stderr_exact != null, | |
| 1058 | .expect_stdout_exact = expect_stdout_exact != null, | |
| 1059 | .expect_stderr_match = expect_stderr_match.items.len != 0, | |
| 1060 | .expect_stdout_match = expect_stdout_match.items.len != 0, | |
| 1061 | .expect_term = expect_term != null, | |
| 1062 | .expect_term_status = if (expect_term) |t| t.status else .exited, | |
| 1063 | }, | |
| 1064 | .file_inputs = .{ .slice = try s.initLazyPathList(run.file_inputs.items) }, | |
| 1065 | .args = .{ .slice = try s.initArgsList(run.argv.items) }, | |
| 1066 | .cwd = .{ .value = try s.addOptionalLazyPath(run.cwd) }, | |
| 1067 | .captured_stdout = .{ .value = if (run.captured_stdout) |cs| .{ | |
| 1068 | .basename = try wc.addString(cs.output.basename), | |
| 1069 | .generated_file = cs.output.generated_file, | |
| 1070 | } else null }, | |
| 1071 | .captured_stderr = .{ .value = if (run.captured_stderr) |cs| .{ | |
| 1072 | .basename = try wc.addString(cs.output.basename), | |
| 1073 | .generated_file = cs.output.generated_file, | |
| 1074 | } else null }, | |
| 1075 | .environ_map = .{ .value = try s.addEnvironMap(run.environ_map) }, | |
| 1076 | .expect_term_value = .{ .value = if (expect_term) |t| t.value else null }, | |
| 1077 | .stdio_limit = .{ .value = run.stdio_limit.toInt() }, | |
| 1078 | .producer = .{ .value = if (run.producer) |cs| s.stepIndex(&cs.step) else null }, | |
| 1079 | .expect_stderr_exact = .{ .value = if (expect_stderr_exact) |bytes| bytes else null }, | |
| 1080 | .expect_stdout_exact = .{ .value = if (expect_stdout_exact) |bytes| bytes else null }, | |
| 1081 | .expect_stderr_match = .{ .slice = expect_stderr_match.items }, | |
| 1082 | .expect_stdout_match = .{ .slice = expect_stdout_match.items }, | |
| 1083 | .stdin = .{ .u = switch (run.stdin) { | |
| 1084 | .none => .none, | |
| 1085 | .bytes => |bytes| .{ .bytes = try wc.addBytes(bytes) }, | |
| 1086 | .lazy_path => |lp| .{ .lazy_path = try s.addLazyPath(lp) }, | |
| 1087 | } }, | |
| 1088 | }); | |
| 1089 | }, | |
| 1090 | .check_file => e: { | |
| 1091 | const cf: *Step.CheckFile = @fieldParentPtr("step", step); | |
| 1092 | break :e try wc.addExtraErased(Configuration.Step.CheckFile, .{ | |
| 1093 | .flags = .{ | |
| 1094 | .expected_exact = cf.expected_exact != null, | |
| 1095 | .expected_matches = cf.expected_matches.len != 0, | |
| 1096 | .max_bytes = cf.max_bytes != null, | |
| 1097 | }, | |
| 1098 | .file = try s.addLazyPath(cf.file), | |
| 1099 | .expected_exact = .{ .value = cf.expected_exact }, | |
| 1100 | .expected_matches = .{ .slice = cf.expected_matches }, | |
| 1101 | .max_bytes = .{ .value = cf.max_bytes }, | |
| 1102 | }); | |
| 1103 | }, | |
| 1104 | .config_header => e: { | |
| 1105 | const ch: *Step.ConfigHeader = @fieldParentPtr("step", step); | |
| 1106 | const lazy_path: ?std.Build.LazyPath = ch.style.getPath(); | |
| 1107 | const pairs = try arena.alloc(Configuration.Step.ConfigHeader.Value.Pair, ch.values.count()); | |
| 1108 | for (pairs, ch.values.keys(), ch.values.values()) |*pair, key, value| pair.* = .{ | |
| 1109 | .key = try wc.addString(key), | |
| 1110 | .index = switch (value) { | |
| 1111 | .undef => .undef, | |
| 1112 | .defined => .defined, | |
| 1113 | .boolean => |x| switch (x) { | |
| 1114 | false => .bool_false, | |
| 1115 | true => .bool_true, | |
| 1116 | }, | |
| 1117 | .int => |x| switch (x) { | |
| 1118 | 0 => .int_0, | |
| 1119 | 1 => .int_1, | |
| 1120 | else => try wc.addExtra(Configuration.Step.ConfigHeader.Value, .initSigned(x)), | |
| 1121 | }, | |
| 1122 | .ident => |x| try wc.addExtra(Configuration.Step.ConfigHeader.Value, .{ | |
| 1123 | .flags = .{ | |
| 1124 | .tag = .ident, | |
| 1125 | .small = 0, | |
| 1126 | }, | |
| 1127 | .i64 = .{ .value = null }, | |
| 1128 | .u64 = .{ .value = null }, | |
| 1129 | .ident = .{ .value = try wc.addString(x) }, | |
| 1130 | .string = .{ .value = null }, | |
| 1131 | }), | |
| 1132 | .string => |x| try wc.addExtra(Configuration.Step.ConfigHeader.Value, .{ | |
| 1133 | .flags = .{ | |
| 1134 | .tag = .string, | |
| 1135 | .small = 0, | |
| 1136 | }, | |
| 1137 | .i64 = .{ .value = null }, | |
| 1138 | .u64 = .{ .value = null }, | |
| 1139 | .ident = .{ .value = null }, | |
| 1140 | .string = .{ .value = try wc.addString(x) }, | |
| 1141 | }), | |
| 1142 | }, | |
| 1143 | }; | |
| 1144 | break :e try wc.addExtraErased(Configuration.Step.ConfigHeader, .{ | |
| 1145 | .flags = .{ | |
| 1146 | .template_file = lazy_path != null, | |
| 1147 | .style = .init(ch.style), | |
| 1148 | .input_size_limit = ch.input_size_limit != null, | |
| 1149 | .include_guard = ch.include_guard != .none, | |
| 1150 | }, | |
| 1151 | .template_file = .{ .value = try s.addOptionalLazyPath(lazy_path) }, | |
| 1152 | .generated_dir = ch.generated_dir, | |
| 1153 | .input_size_limit = .{ .value = ch.input_size_limit }, | |
| 1154 | .include_path = try wc.addString(ch.include_path), | |
| 1155 | .include_guard = .{ .value = ch.include_guard.unwrap() }, | |
| 1156 | .values = .{ .slice = pairs }, | |
| 1157 | }); | |
| 1158 | }, | |
| 1159 | .obj_copy => e: { | |
| 1160 | const oc: *Step.ObjCopy = @fieldParentPtr("step", step); | |
| 1161 | ||
| 1162 | const debug_basename: ?Configuration.String = if (oc.debug_file) |df| | |
| 1163 | df.basename.unwrap() | |
| 1164 | else | |
| 1165 | null; | |
| 1166 | ||
| 1167 | const debug_file: ?Configuration.GeneratedFileIndex = if (oc.debug_file) |df| | |
| 1168 | df.output_file | |
| 1169 | else | |
| 1170 | null; | |
| 1171 | ||
| 1172 | const add_sections = try arena.alloc( | |
| 1173 | Configuration.Step.ObjCopy.AddSection, | |
| 1174 | oc.add_sections.items.len, | |
| 1175 | ); | |
| 1176 | for (add_sections, oc.add_sections.items) |*dest, src| dest.* = .{ | |
| 1177 | .section_name = src.section_name, | |
| 1178 | .file_path = try s.addLazyPath(src.file_path), | |
| 1179 | }; | |
| 1180 | ||
| 1181 | break :e try wc.addExtraErased(Configuration.Step.ObjCopy, .{ | |
| 1182 | .flags = .{ | |
| 1183 | .basename = oc.basename != .none, | |
| 1184 | .debug_file = debug_file != null, | |
| 1185 | .debug_basename = debug_basename != null, | |
| 1186 | .format = .init(oc.format), | |
| 1187 | .strip = oc.strip, | |
| 1188 | .compress_debug = oc.compress_debug, | |
| 1189 | .only_section = oc.only_section != .none, | |
| 1190 | .pad_to = oc.pad_to != null, | |
| 1191 | .add_section = add_sections.len != 0, | |
| 1192 | .update_section = oc.update_sections.items.len != 0, | |
| 1193 | }, | |
| 1194 | .input_file = try s.addLazyPath(oc.input_file), | |
| 1195 | .output_file = oc.output_file, | |
| 1196 | .basename = .{ .value = oc.basename.unwrap() }, | |
| 1197 | .debug_file = .{ .value = debug_file }, | |
| 1198 | .debug_basename = .{ .value = debug_basename }, | |
| 1199 | .only_section = .{ .value = oc.only_section.unwrap() }, | |
| 1200 | .pad_to = .{ .value = oc.pad_to }, | |
| 1201 | .add_section = .{ .slice = add_sections }, | |
| 1202 | .update_section = .{ .slice = oc.update_sections.items }, | |
| 1203 | }); | |
| 1204 | }, | |
| 1205 | .options => e: { | |
| 1206 | const so: *Step.Options = @fieldParentPtr("step", step); | |
| 1207 | ||
| 1208 | const args = try arena.alloc(Configuration.Step.Options.Arg, so.args.items.len); | |
| 1209 | for (args, so.args.items) |*dest, src| dest.* = .{ | |
| 1210 | .name = src.name, | |
| 1211 | .path = try s.addLazyPath(src.path), | |
| 1212 | }; | |
| 1213 | ||
| 1214 | break :e try wc.addExtraErased(Configuration.Step.Options, .{ | |
| 1215 | .flags = .{ | |
| 1216 | .args = so.args.items.len != 0, | |
| 1217 | }, | |
| 1218 | .generated_file = so.generated_file, | |
| 1219 | .contents = try wc.addBytes(so.contents.items), | |
| 1220 | .args = .{ .slice = args }, | |
| 1221 | }); | |
| 1222 | }, | |
| 1223 | }), | |
| 1224 | }); | |
| 1225 | } | |
| 1226 | } | |
| 1227 | ||
| 1228 | try wc.unlazy_deps.ensureUnusedCapacity(gpa, graph.needed_lazy_dependencies.keys().len); | |
| 1229 | for (graph.needed_lazy_dependencies.keys()) |k| { | |
| 1230 | wc.unlazy_deps.appendAssumeCapacity(try wc.addString(k)); | |
| 1231 | } | |
| 1232 | ||
| 1233 | try wc.write(writer, .{ | |
| 1234 | .default_step = s.stepIndex(b.default_step), | |
| 1235 | .generated_files_len = @intCast(graph.generated_files.items.len), | |
| 1236 | .poisoned = switch (graph.cache_poison) { | |
| 1237 | .pure, .disallowed, .ignored => false, | |
| 1238 | .poisoned => true, | |
| 1239 | }, | |
| 1240 | }); | |
| 1241 | } | |
| 1242 | ||
| 1243 | fn addOptionalResolvedTarget( | |
| 1244 | wc: *Configuration.Wip, | |
| 1245 | optional_resolved_target: ?std.Build.ResolvedTarget, | |
| 1246 | ) !Configuration.ResolvedTarget.OptionalIndex { | |
| 1247 | const resolved_target = optional_resolved_target orelse return .none; | |
| 1248 | return .init(try wc.addDeduped(Configuration.ResolvedTarget, .{ | |
| 1249 | .query = try wc.addTargetQuery(&resolved_target.query), | |
| 1250 | .result = try wc.addTarget(resolved_target.result), | |
| 1251 | })); | |
| 1252 | } | |
| 1253 | ||
| 1254 | fn addInstallDir(wc: *Configuration.Wip, install_dir: ?std.Build.InstallDir) !Configuration.InstallDestDir { | |
| 1255 | switch (install_dir orelse return .none) { | |
| 1256 | .prefix => return .prefix, | |
| 1257 | .lib => return .lib, | |
| 1258 | .bin => return .bin, | |
| 1259 | .header => return .header, | |
| 1260 | .custom => |sub_path| return .initCustom(try wc.addString(sub_path)), | |
| 1261 | } | |
| 1262 | } | |
| 1263 | ||
| 1264 | fn addInstallDirDefaultNull(wc: *Configuration.Wip, install_dir: ?std.Build.InstallDir) !?Configuration.InstallDestDir { | |
| 1265 | return try addInstallDir(wc, install_dir orelse return null); | |
| 1266 | } | |
| 1267 | ||
| 1268 | /// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which | |
| 1269 | /// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`. | |
| 1270 | fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { | |
| 1271 | const root_module = if (step.cast(Step.Compile)) |cs| root: { | |
| 1272 | break :root cs.root_module; | |
| 1273 | } else return; // not a compile step so no module dependencies | |
| 1274 | ||
| 1275 | // Starting from `root_module`, discover all modules in this graph. | |
| 1276 | const modules = root_module.getGraph().modules; | |
| 1277 | ||
| 1278 | // For each of those modules, set up the implied step dependencies. | |
| 1279 | for (modules) |mod| { | |
| 1280 | if (mod.root_source_file) |lp| lp.addStepDependencies(step); | |
| 1281 | for (mod.include_dirs.items) |include_dir| switch (include_dir) { | |
| 1282 | .path, | |
| 1283 | .path_system, | |
| 1284 | .path_after, | |
| 1285 | .framework_path, | |
| 1286 | .framework_path_system, | |
| 1287 | .embed_path, | |
| 1288 | => |lp| lp.addStepDependencies(step), | |
| 1289 | ||
| 1290 | .other_step => |other| { | |
| 1291 | other.getEmittedIncludeTree().addStepDependencies(step); | |
| 1292 | step.dependOn(&other.step); | |
| 1293 | }, | |
| 1294 | ||
| 1295 | .config_header_step => |other| step.dependOn(&other.step), | |
| 1296 | }; | |
| 1297 | for (mod.lib_paths.items) |lp| lp.addStepDependencies(step); | |
| 1298 | for (mod.rpaths.items) |rpath| switch (rpath) { | |
| 1299 | .lazy_path => |lp| lp.addStepDependencies(step), | |
| 1300 | .special => {}, | |
| 1301 | }; | |
| 1302 | for (mod.link_objects.items) |link_object| switch (link_object) { | |
| 1303 | .static_path, | |
| 1304 | .assembly_file, | |
| 1305 | => |lp| lp.addStepDependencies(step), | |
| 1306 | .other_step => |other| step.dependOn(&other.step), | |
| 1307 | .system_lib => {}, | |
| 1308 | .c_source_file => |source| source.file.addStepDependencies(step), | |
| 1309 | .c_source_files => |source_files| source_files.root.addStepDependencies(step), | |
| 1310 | .win32_resource_file => |rc_source| { | |
| 1311 | rc_source.file.addStepDependencies(step); | |
| 1312 | for (rc_source.include_paths) |lp| lp.addStepDependencies(step); | |
| 1313 | }, | |
| 1314 | }; | |
| 1315 | } | |
| 1316 | } | |
| 1317 | ||
| 1318 | fn nextArg(args: []const [:0]const u8, idx: *usize) ?[:0]const u8 { | |
| 1319 | if (idx.* >= args.len) return null; | |
| 1320 | defer idx.* += 1; | |
| 1321 | return args[idx.*]; | |
| 1322 | } | |
| 1323 | ||
| 1324 | fn nextArgOrFatal(args: []const [:0]const u8, idx: *usize) [:0]const u8 { | |
| 1325 | return nextArg(args, idx) orelse { | |
| 1326 | fatalWithHint("expected argument after {q}", .{args[idx.* - 1]}); | |
| 1327 | }; | |
| 1328 | } | |
| 1329 | ||
| 1330 | fn expectArgOrFatal(args: []const [:0]const u8, index_ptr: *usize, first: []const u8) []const u8 { | |
| 1331 | const next_arg = nextArg(args, index_ptr) orelse fatal("missing {q} argument", .{first}); | |
| 1332 | if (!mem.eql(u8, first, next_arg)) fatal("expected {q} instead of {q}", .{ first, next_arg }); | |
| 1333 | const arg = nextArg(args, index_ptr) orelse fatal("expected argument after {q}", .{first}); | |
| 1334 | return arg; | |
| 1335 | } | |
| 1336 | ||
| 1337 | const ErrorStyle = enum { | |
| 1338 | verbose, | |
| 1339 | minimal, | |
| 1340 | verbose_clear, | |
| 1341 | minimal_clear, | |
| 1342 | fn verboseContext(s: ErrorStyle) bool { | |
| 1343 | return switch (s) { | |
| 1344 | .verbose, .verbose_clear => true, | |
| 1345 | .minimal, .minimal_clear => false, | |
| 1346 | }; | |
| 1347 | } | |
| 1348 | fn clearOnUpdate(s: ErrorStyle) bool { | |
| 1349 | return switch (s) { | |
| 1350 | .verbose, .minimal => false, | |
| 1351 | .verbose_clear, .minimal_clear => true, | |
| 1352 | }; | |
| 1353 | } | |
| 1354 | }; | |
| 1355 | const MultilineErrors = enum { indent, newline, none }; | |
| 1356 | const Summary = enum { all, new, failures, line, none }; | |
| 1357 | ||
| 1358 | fn fatalWithHint(comptime f: []const u8, args: anytype) noreturn { | |
| 1359 | log.info("to access the help menu: zig build -h", .{}); | |
| 1360 | fatal(f, args); | |
| 1361 | } | |
| 1362 | ||
| 1363 | fn serializeSystemIntegrationOptions(graph: *std.Build.Graph, wc: *Configuration.Wip) Allocator.Error!void { | |
| 1364 | const gpa = wc.gpa; | |
| 1365 | ||
| 1366 | var bad = false; | |
| 1367 | try wc.system_integrations.ensureTotalCapacityPrecise(gpa, graph.system_integration_options.entries.len); | |
| 1368 | for (graph.system_integration_options.keys(), graph.system_integration_options.values()) |k, v| { | |
| 1369 | wc.system_integrations.appendAssumeCapacity(.{ | |
| 1370 | .name = try wc.addString(k), | |
| 1371 | .status = switch (v) { | |
| 1372 | .user_disabled, .user_enabled => x: { | |
| 1373 | // The user tried to enable or disable a system library integration, but | |
| 1374 | // the configure script did not recognize that option. | |
| 1375 | log.err("system integration name not recognized by configure script: {s}", .{k}); | |
| 1376 | bad = true; | |
| 1377 | break :x .disabled; | |
| 1378 | }, | |
| 1379 | .declared_disabled => .disabled, | |
| 1380 | .declared_enabled => .enabled, | |
| 1381 | }, | |
| 1382 | }); | |
| 1383 | } | |
| 1384 | if (bad) { | |
| 1385 | log.info("help menu contains available options: zig build -h", .{}); | |
| 1386 | process.exit(1); | |
| 1387 | } | |
| 1388 | } | |
| 1389 | ||
| 1390 | fn serializePackageOptions(b: *std.Build, wc: *Configuration.Wip) Allocator.Error!void { | |
| 1391 | const gpa = wc.gpa; | |
| 1392 | ||
| 1393 | try wc.available_options.ensureTotalCapacityPrecise(gpa, b.available_options_map.count()); | |
| 1394 | for (b.available_options_map.keys(), b.available_options_map.values()) |name, *opt| { | |
| 1395 | wc.available_options.appendAssumeCapacity(.{ | |
| 1396 | .name = try wc.addString(name), | |
| 1397 | .description = try wc.addString(opt.description), | |
| 1398 | .type = opt.type_id, | |
| 1399 | .enum_options = if (opt.enum_options) |enum_vals| .init(try wc.addStringList(enum_vals)) else .none, | |
| 1400 | }); | |
| 1401 | } | |
| 1402 | } |
lib/compiler/std-docs.zig+16-37| ... | ... | @@ -271,12 +271,16 @@ fn serveWasm( |
| 271 | 271 | // Do the compilation every request, so that the user can edit the files |
| 272 | 272 | // and see the changes without restarting the server. |
| 273 | 273 | const wasm_base_path = try buildWasmBinary(arena, context, optimize_mode); |
| 274 | const target = std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{ | |
| 275 | .arch_os_abi = autodoc_arch_os_abi, | |
| 276 | .cpu_features = autodoc_cpu_features, | |
| 277 | }) catch unreachable) catch unreachable; | |
| 274 | 278 | const bin_name = try std.zig.binNameAlloc(arena, .{ |
| 275 | 279 | .root_name = autodoc_root_name, |
| 276 | .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{ | |
| 277 | .arch_os_abi = autodoc_arch_os_abi, | |
| 278 | .cpu_features = autodoc_cpu_features, | |
| 279 | }) catch unreachable) catch unreachable), | |
| 280 | .cpu_arch = target.cpu.arch, | |
| 281 | .os_tag = target.os.tag, | |
| 282 | .ofmt = target.ofmt, | |
| 283 | .abi = target.abi, | |
| 280 | 284 | .output_mode = .Exe, |
| 281 | 285 | }); |
| 282 | 286 | // std.http.Server does not have a sendfile API yet. |
| ... | ... | @@ -406,51 +410,26 @@ fn buildWasmBinary( |
| 406 | 410 | child.stdin.?.close(io); |
| 407 | 411 | child.stdin = null; |
| 408 | 412 | |
| 409 | switch (try child.wait(io)) { | |
| 410 | .exited => |code| { | |
| 411 | if (code != 0) { | |
| 412 | std.log.err( | |
| 413 | "the following command exited with error code {d}:\n{s}", | |
| 414 | .{ code, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, | |
| 415 | ); | |
| 416 | return error.WasmCompilationFailed; | |
| 417 | } | |
| 418 | }, | |
| 419 | .signal => |sig| { | |
| 420 | std.log.err( | |
| 421 | "the following command terminated with signal {t}:\n{s}", | |
| 422 | .{ sig, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, | |
| 423 | ); | |
| 424 | return error.WasmCompilationFailed; | |
| 425 | }, | |
| 426 | .stopped => |sig| { | |
| 427 | std.log.err( | |
| 428 | "the following command stopped unexpectedly with signal {t}:\n{s}", | |
| 429 | .{ sig, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, | |
| 430 | ); | |
| 431 | return error.WasmCompilationFailed; | |
| 432 | }, | |
| 433 | .unknown => { | |
| 434 | std.log.err( | |
| 435 | "the following command terminated unexpectedly:\n{s}", | |
| 436 | .{try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)}, | |
| 437 | ); | |
| 438 | return error.WasmCompilationFailed; | |
| 439 | }, | |
| 413 | const term = try child.wait(io); | |
| 414 | if (!term.success()) { | |
| 415 | std.log.err("the following command {f}:\n{s}", .{ | |
| 416 | term, try std.zig.allocPrintCmd(arena, argv.items, .{}), | |
| 417 | }); | |
| 418 | return error.WasmCompilationFailed; | |
| 440 | 419 | } |
| 441 | 420 | |
| 442 | 421 | if (result_error_bundle.errorMessageCount() > 0) { |
| 443 | 422 | try result_error_bundle.renderToStderr(io, .{}, .auto); |
| 444 | 423 | std.log.err("the following command failed with {d} compilation errors:\n{s}", .{ |
| 445 | 424 | result_error_bundle.errorMessageCount(), |
| 446 | try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), | |
| 425 | try std.zig.allocPrintCmd(arena, argv.items, .{}), | |
| 447 | 426 | }); |
| 448 | 427 | return error.WasmCompilationFailed; |
| 449 | 428 | } |
| 450 | 429 | |
| 451 | 430 | return result orelse { |
| 452 | 431 | std.log.err("child process failed to report result\n{s}", .{ |
| 453 | try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), | |
| 432 | try std.zig.allocPrintCmd(arena, argv.items, .{}), | |
| 454 | 433 | }); |
| 455 | 434 | return error.WasmCompilationFailed; |
| 456 | 435 | }; |
lib/init/build.zig+1-3| ... | ... | @@ -111,9 +111,7 @@ pub fn build(b: *std.Build) void { |
| 111 | 111 | |
| 112 | 112 | // This allows the user to pass arguments to the application in the build |
| 113 | 113 | // command itself, like this: `zig build run -- arg1 arg2 etc` |
| 114 | if (b.args) |args| { | |
| 115 | run_cmd.addArgs(args); | |
| 116 | } | |
| 114 | run_cmd.addPassthruArgs(); | |
| 117 | 115 | |
| 118 | 116 | // Creates an executable that will run `test` blocks from the provided module. |
| 119 | 117 | // Here `mod` needs to define a target, which is why earlier we made sure to |
lib/std/Build.zig+639-713| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const Build = @This(); |
| 2 | ||
| 2 | 3 | const builtin = @import("builtin"); |
| 3 | 4 | |
| 4 | 5 | const std = @import("std.zig"); |
| ... | ... | @@ -19,48 +20,23 @@ const ArrayList = std.ArrayList; |
| 19 | 20 | pub const Cache = @import("Build/Cache.zig"); |
| 20 | 21 | pub const Step = @import("Build/Step.zig"); |
| 21 | 22 | pub const Module = @import("Build/Module.zig"); |
| 22 | pub const Watch = @import("Build/Watch.zig"); | |
| 23 | pub const Fuzz = @import("Build/Fuzz.zig"); | |
| 24 | pub const WebServer = @import("Build/WebServer.zig"); | |
| 25 | 23 | pub const abi = @import("Build/abi.zig"); |
| 24 | /// The serialized output of configure phase ingested by make phase. | |
| 25 | pub const Configuration = @import("Build/Configuration.zig"); | |
| 26 | 26 | |
| 27 | 27 | /// Shared state among all Build instances. |
| 28 | 28 | graph: *Graph, |
| 29 | install_tls: TopLevelStep, | |
| 30 | uninstall_tls: TopLevelStep, | |
| 29 | install_tls: Step.TopLevel, | |
| 30 | uninstall_tls: Step.TopLevel, | |
| 31 | 31 | allocator: Allocator, |
| 32 | 32 | user_input_options: UserInputOptionsMap, |
| 33 | available_options_map: AvailableOptionsMap, | |
| 34 | available_options_list: std.array_list.Managed(AvailableOption), | |
| 35 | verbose: bool, | |
| 36 | verbose_link: bool, | |
| 37 | verbose_cc: bool, | |
| 38 | verbose_air: bool, | |
| 39 | verbose_llvm_ir: ?[]const u8, | |
| 40 | verbose_llvm_bc: ?[]const u8, | |
| 41 | verbose_llvm_cpu_features: bool, | |
| 42 | reference_trace: ?u32 = null, | |
| 33 | available_options_map: std.array_hash_map.String(AvailableOption) = .empty, | |
| 43 | 34 | invalid_user_input: bool, |
| 44 | 35 | default_step: *Step, |
| 45 | top_level_steps: std.StringArrayHashMapUnmanaged(*TopLevelStep), | |
| 46 | install_prefix: []const u8, | |
| 47 | dest_dir: ?[]const u8, | |
| 48 | lib_dir: []const u8, | |
| 49 | exe_dir: []const u8, | |
| 50 | h_dir: []const u8, | |
| 51 | install_path: []const u8, | |
| 52 | sysroot: ?[]const u8 = null, | |
| 53 | search_prefixes: ArrayList([]const u8), | |
| 54 | libc_file: ?[]const u8 = null, | |
| 36 | top_level_steps: std.StringArrayHashMapUnmanaged(*Step.TopLevel), | |
| 55 | 37 | /// Path to the directory containing build.zig. |
| 56 | build_root: Cache.Directory, | |
| 57 | cache_root: Cache.Directory, | |
| 58 | pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null, | |
| 59 | args: ?[]const []const u8 = null, | |
| 38 | root: Cache.Path, | |
| 60 | 39 | debug_log_scopes: []const []const u8 = &.{}, |
| 61 | debug_compile_errors: bool = false, | |
| 62 | debug_incremental: bool = false, | |
| 63 | debug_pkg_config: bool = false, | |
| 64 | 40 | /// Number of stack frames captured when a `StackTrace` is recorded for debug purposes, |
| 65 | 41 | /// in particular at `Step` creation. |
| 66 | 42 | /// Set to 0 to disable stack collection. |
| ... | ... | @@ -76,12 +52,6 @@ enable_rosetta: bool = false, |
| 76 | 52 | enable_wasmtime: bool = false, |
| 77 | 53 | /// Use system Wine installation to run cross compiled Windows build artifacts. |
| 78 | 54 | enable_wine: bool = false, |
| 79 | /// After following the steps in https://codeberg.org/ziglang/infra/src/branch/master/libc-update/glibc.md, | |
| 80 | /// this will be the directory $glibc-build-dir/install/glibcs | |
| 81 | /// Given the example of the aarch64 target, this is the directory | |
| 82 | /// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`. | |
| 83 | /// Also works for dynamic musl. | |
| 84 | libc_runtimes_dir: ?[]const u8 = null, | |
| 85 | 55 | |
| 86 | 56 | dep_prefix: []const u8 = "", |
| 87 | 57 | |
| ... | ... | @@ -94,10 +64,6 @@ pkg_hash: []const u8, |
| 94 | 64 | /// A mapping from dependency names to package hashes. |
| 95 | 65 | available_deps: AvailableDeps, |
| 96 | 66 | |
| 97 | release_mode: ReleaseMode, | |
| 98 | ||
| 99 | build_id: ?std.zig.BuildId = null, | |
| 100 | ||
| 101 | 67 | pub const ReleaseMode = enum { |
| 102 | 68 | off, |
| 103 | 69 | any, |
| ... | ... | @@ -112,33 +78,145 @@ pub const Graph = struct { |
| 112 | 78 | io: Io, |
| 113 | 79 | /// Process lifetime. |
| 114 | 80 | arena: Allocator, |
| 115 | system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty, | |
| 81 | system_integration_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .empty, | |
| 116 | 82 | system_package_mode: bool = false, |
| 117 | debug_compiler_runtime_libs: ?std.builtin.OptimizeMode = null, | |
| 118 | cache: Cache, | |
| 119 | zig_exe: [:0]const u8, | |
| 83 | zig_exe: []const u8, | |
| 120 | 84 | environ_map: process.Environ.Map, |
| 121 | global_cache_root: Cache.Directory, | |
| 122 | zig_lib_directory: Cache.Directory, | |
| 123 | 85 | needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .empty, |
| 124 | 86 | /// Information about the native target. Computed before build() is invoked. |
| 125 | 87 | host: ResolvedTarget, |
| 126 | incremental: ?bool = null, | |
| 127 | random_seed: u32 = 0, | |
| 128 | 88 | dependency_cache: InitializedDepMap = .empty, |
| 129 | 89 | allow_so_scripts: ?bool = null, |
| 130 | /// Steps should use `io` to limit the number of jobs, however in the case of | |
| 131 | /// a single step spawning a fixed number of processes this can be used. | |
| 132 | max_jobs: ?u32 = null, | |
| 133 | time_report: bool, | |
| 90 | time_report: bool = false, | |
| 91 | verbose: bool = false, | |
| 134 | 92 | /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also |
| 135 | 93 | /// respects the '--color' flag. |
| 136 | 94 | stderr_mode: ?Io.Terminal.Mode = null, |
| 95 | release_mode: ReleaseMode = .off, | |
| 96 | ||
| 97 | /// Indexes correspond to `Configuration.GeneratedFileIndex`. | |
| 98 | generated_files: std.ArrayList(*Step), | |
| 99 | wip_configuration: Configuration.Wip, | |
| 100 | ||
| 101 | cache_poison: CachePoison = .pure, | |
| 102 | /// Observing this data causes cache poisoning. See `CachePoison`. | |
| 103 | search_prefixes: std.ArrayList([]const u8) = .empty, | |
| 104 | ||
| 105 | /// If the cache is poisoned means that the **configure logic** had side | |
| 106 | /// effects, or otherwise did something that could not be tracked by the | |
| 107 | /// cache system. | |
| 108 | /// | |
| 109 | /// This is not to be confused with whether individual steps may have side | |
| 110 | /// effects when being evaluated; it has to do with the logic inside build.zig | |
| 111 | /// itself. For example, a `Run` step that prints "hello world" has side | |
| 112 | /// effects *at make time* and therefore does not warrant setting this flag, | |
| 113 | /// while checking for the existence of `scdoc` *at configure time* in order to | |
| 114 | /// choose the default value for a configuration option does. | |
| 115 | /// | |
| 116 | /// Keeping the cache pure will make `zig build` faster, bypassing the | |
| 117 | /// configurer process when identical configuration would be generated. | |
| 118 | /// | |
| 119 | /// When the cache is poisoned, the maker process will delete the build | |
| 120 | /// configuration file upon ingesting it since it cannot be reused. | |
| 121 | pub const CachePoison = enum { | |
| 122 | pure, | |
| 123 | poisoned, | |
| 124 | /// Indicates the user would like to see a stack trace if the cache | |
| 125 | /// would become poisoned. | |
| 126 | disallowed, | |
| 127 | /// Indicates the user would like to ignore the cache being poisoned | |
| 128 | /// and cache anyway, opting into cache hits on stale configuration. | |
| 129 | ignored, | |
| 130 | }; | |
| 131 | ||
| 132 | pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex { | |
| 133 | graph.generated_files.append(graph.arena, owner) catch @panic("OOM"); | |
| 134 | return @enumFromInt(graph.generated_files.items.len - 1); | |
| 135 | } | |
| 136 | ||
| 137 | pub fn dupeString(graph: *const Graph, bytes: []const u8) []const u8 { | |
| 138 | return graph.arena.dupe(u8, bytes) catch @panic("OOM"); | |
| 139 | } | |
| 140 | ||
| 141 | pub fn dupePath(graph: *const Graph, bytes: []const u8) []const u8 { | |
| 142 | return dupePathInner(graph.arena, bytes); | |
| 143 | } | |
| 144 | ||
| 145 | fn dupePathInner(arena: Allocator, bytes: []const u8) []const u8 { | |
| 146 | if (builtin.os.tag != .windows) return arena.dupe(u8, bytes) catch @panic("OOM"); | |
| 147 | const the_copy = arena.dupe(u8, bytes) catch @panic("OOM"); | |
| 148 | mem.replaceScalar(u8, the_copy, '/', '\\'); | |
| 149 | return the_copy; | |
| 150 | } | |
| 151 | ||
| 152 | pub fn dupeStrings(graph: *const Graph, strings: []const []const u8) []const []const u8 { | |
| 153 | const array = graph.alloc([]const u8, strings.len); | |
| 154 | for (array, strings) |*dest, source| dest.* = dupeString(graph, source); | |
| 155 | return array; | |
| 156 | } | |
| 157 | ||
| 158 | /// An absolute path or a path relative to the current working directory of | |
| 159 | /// the build runner process. | |
| 160 | /// | |
| 161 | /// Use of this function indicates a dependency on the host system. | |
| 162 | pub fn cwdRelativePath(graph: *Graph, sub_path: []const u8) LazyPath { | |
| 163 | return @This().path(graph, .cwd, sub_path); | |
| 164 | } | |
| 165 | ||
| 166 | /// A path whose components and contents are known at some point during | |
| 167 | /// `Step` resolution, relative to the provided base directory. | |
| 168 | pub fn path(graph: *Graph, base: Configuration.Path.Base, sub_path: []const u8) LazyPath { | |
| 169 | return .{ .relative = .{ | |
| 170 | .base = base, | |
| 171 | .sub_path = @This().dupePath(graph, sub_path), | |
| 172 | } }; | |
| 173 | } | |
| 174 | ||
| 175 | /// Allocates using the global process arena, failing the build on | |
| 176 | /// allocation failure. | |
| 177 | pub fn alloc(graph: *const Graph, comptime T: type, n: usize) []T { | |
| 178 | return graph.arena.allocAdvancedWithRetAddr(T, null, n, @returnAddress()) catch @panic("OOM"); | |
| 179 | } | |
| 180 | ||
| 181 | /// Allocates using the global process arena, failing the build on | |
| 182 | /// allocation failure. | |
| 183 | pub fn create(graph: *const Graph, comptime T: type) *T { | |
| 184 | return @ptrCast(graph.arena.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress()) catch @panic("OOM")); | |
| 185 | } | |
| 186 | ||
| 187 | pub fn addBytesList(graph: *Graph, bytes_list: []const []const u8) []const Configuration.Bytes { | |
| 188 | const result = graph.alloc(Configuration.Bytes, bytes_list.len); | |
| 189 | for (result, bytes_list) |*d, s| d.* = addBytes(graph, s); | |
| 190 | return result; | |
| 191 | } | |
| 192 | ||
| 193 | pub fn addBytes(graph: *Graph, bytes: []const u8) Configuration.Bytes { | |
| 194 | const wc = &graph.wip_configuration; | |
| 195 | return wc.addBytes(bytes) catch @panic("OOM"); | |
| 196 | } | |
| 197 | ||
| 198 | pub fn addString(graph: *Graph, bytes: []const u8) Configuration.String { | |
| 199 | const wc = &graph.wip_configuration; | |
| 200 | return wc.addString(bytes) catch @panic("OOM"); | |
| 201 | } | |
| 202 | ||
| 203 | /// Indicates that the **configure logic** had side effects, or otherwise | |
| 204 | /// did something that could not be tracked by the cache system. | |
| 205 | /// | |
| 206 | /// See `CachePoison` documentation for more details. | |
| 207 | pub fn poisonCache(graph: *Graph) void { | |
| 208 | switch (graph.cache_poison) { | |
| 209 | .pure => graph.cache_poison = .poisoned, | |
| 210 | .poisoned => return, | |
| 211 | .disallowed => @panic("cache poisoned"), | |
| 212 | .ignored => log.warn("ignoring cache poisoning", .{}), | |
| 213 | } | |
| 214 | } | |
| 137 | 215 | }; |
| 138 | 216 | |
| 139 | 217 | const AvailableDeps = []const struct { []const u8, []const u8 }; |
| 140 | 218 | |
| 141 | const SystemLibraryMode = enum { | |
| 219 | pub const SystemLibraryMode = enum { | |
| 142 | 220 | /// User asked for the library to be disabled. |
| 143 | 221 | /// The build runner has not confirmed whether the setting is recognized yet. |
| 144 | 222 | user_disabled, |
| ... | ... | @@ -187,31 +265,11 @@ const InitializedDepContext = struct { |
| 187 | 265 | } |
| 188 | 266 | }; |
| 189 | 267 | |
| 190 | pub const RunError = error{ | |
| 191 | ReadFailure, | |
| 192 | ExitCodeFailure, | |
| 193 | ProcessTerminated, | |
| 194 | ExecNotSupported, | |
| 195 | } || std.process.SpawnError; | |
| 196 | ||
| 197 | pub const PkgConfigError = error{ | |
| 198 | PkgConfigCrashed, | |
| 199 | PkgConfigFailed, | |
| 200 | PkgConfigNotInstalled, | |
| 201 | PkgConfigInvalidOutput, | |
| 202 | }; | |
| 203 | ||
| 204 | pub const PkgConfigPkg = struct { | |
| 205 | name: []const u8, | |
| 206 | desc: []const u8, | |
| 207 | }; | |
| 208 | ||
| 209 | 268 | const UserInputOptionsMap = StringHashMap(UserInputOption); |
| 210 | const AvailableOptionsMap = StringHashMap(AvailableOption); | |
| 211 | 269 | |
| 212 | 270 | const AvailableOption = struct { |
| 213 | 271 | name: []const u8, |
| 214 | type_id: TypeId, | |
| 272 | type_id: Configuration.AvailableOption.Type, | |
| 215 | 273 | description: []const u8, |
| 216 | 274 | /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options |
| 217 | 275 | enum_options: ?[]const []const u8, |
| ... | ... | @@ -232,36 +290,9 @@ const UserValue = union(enum) { |
| 232 | 290 | lazy_path_list: std.array_list.Managed(LazyPath), |
| 233 | 291 | }; |
| 234 | 292 | |
| 235 | const TypeId = enum { | |
| 236 | bool, | |
| 237 | int, | |
| 238 | float, | |
| 239 | @"enum", | |
| 240 | enum_list, | |
| 241 | string, | |
| 242 | list, | |
| 243 | build_id, | |
| 244 | lazy_path, | |
| 245 | lazy_path_list, | |
| 246 | }; | |
| 247 | ||
| 248 | const TopLevelStep = struct { | |
| 249 | pub const base_id: Step.Id = .top_level; | |
| 250 | ||
| 251 | step: Step, | |
| 252 | description: []const u8, | |
| 253 | }; | |
| 254 | ||
| 255 | pub const DirList = struct { | |
| 256 | lib_dir: ?[]const u8 = null, | |
| 257 | exe_dir: ?[]const u8 = null, | |
| 258 | include_dir: ?[]const u8 = null, | |
| 259 | }; | |
| 260 | ||
| 261 | 293 | pub fn create( |
| 262 | 294 | graph: *Graph, |
| 263 | build_root: Cache.Directory, | |
| 264 | cache_root: Cache.Directory, | |
| 295 | root: Cache.Path, | |
| 265 | 296 | available_deps: AvailableDeps, |
| 266 | 297 | ) error{OutOfMemory}!*Build { |
| 267 | 298 | const arena = graph.arena; |
| ... | ... | @@ -269,31 +300,15 @@ pub fn create( |
| 269 | 300 | const b = try arena.create(Build); |
| 270 | 301 | b.* = .{ |
| 271 | 302 | .graph = graph, |
| 272 | .build_root = build_root, | |
| 273 | .cache_root = cache_root, | |
| 274 | .verbose = false, | |
| 275 | .verbose_link = false, | |
| 276 | .verbose_cc = false, | |
| 277 | .verbose_air = false, | |
| 278 | .verbose_llvm_ir = null, | |
| 279 | .verbose_llvm_bc = null, | |
| 280 | .verbose_llvm_cpu_features = false, | |
| 303 | .root = root, | |
| 281 | 304 | .invalid_user_input = false, |
| 282 | 305 | .allocator = arena, |
| 283 | 306 | .user_input_options = UserInputOptionsMap.init(arena), |
| 284 | .available_options_map = AvailableOptionsMap.init(arena), | |
| 285 | .available_options_list = std.array_list.Managed(AvailableOption).init(arena), | |
| 286 | 307 | .top_level_steps = .{}, |
| 287 | 308 | .default_step = undefined, |
| 288 | .search_prefixes = .empty, | |
| 289 | .install_prefix = undefined, | |
| 290 | .lib_dir = undefined, | |
| 291 | .exe_dir = undefined, | |
| 292 | .h_dir = undefined, | |
| 293 | .dest_dir = graph.environ_map.get("DESTDIR"), | |
| 294 | 309 | .install_tls = .{ |
| 295 | 310 | .step = .init(.{ |
| 296 | .id = TopLevelStep.base_id, | |
| 311 | .tag = .top_level, | |
| 297 | 312 | .name = "install", |
| 298 | 313 | .owner = b, |
| 299 | 314 | }), |
| ... | ... | @@ -301,21 +316,17 @@ pub fn create( |
| 301 | 316 | }, |
| 302 | 317 | .uninstall_tls = .{ |
| 303 | 318 | .step = .init(.{ |
| 304 | .id = TopLevelStep.base_id, | |
| 319 | .tag = .top_level, | |
| 305 | 320 | .name = "uninstall", |
| 306 | 321 | .owner = b, |
| 307 | .makeFn = makeUninstall, | |
| 308 | 322 | }), |
| 309 | 323 | .description = "Remove build artifacts from prefix path", |
| 310 | 324 | }, |
| 311 | .install_path = undefined, | |
| 312 | .args = null, | |
| 313 | 325 | .modules = .empty, |
| 314 | 326 | .named_writefiles = .empty, |
| 315 | 327 | .named_lazy_paths = .empty, |
| 316 | 328 | .pkg_hash = "", |
| 317 | 329 | .available_deps = available_deps, |
| 318 | .release_mode = .off, | |
| 319 | 330 | }; |
| 320 | 331 | try b.top_level_steps.put(arena, b.install_tls.step.name, &b.install_tls); |
| 321 | 332 | try b.top_level_steps.put(arena, b.uninstall_tls.step.name, &b.uninstall_tls); |
| ... | ... | @@ -326,32 +337,20 @@ pub fn create( |
| 326 | 337 | fn createChild( |
| 327 | 338 | parent: *Build, |
| 328 | 339 | dep_name: []const u8, |
| 329 | build_root: Cache.Directory, | |
| 330 | pkg_hash: []const u8, | |
| 331 | pkg_deps: AvailableDeps, | |
| 332 | user_input_options: UserInputOptionsMap, | |
| 333 | ) error{OutOfMemory}!*Build { | |
| 334 | const child = try createChildOnly(parent, dep_name, build_root, pkg_hash, pkg_deps, user_input_options); | |
| 335 | try determineAndApplyInstallPrefix(child); | |
| 336 | return child; | |
| 337 | } | |
| 338 | ||
| 339 | fn createChildOnly( | |
| 340 | parent: *Build, | |
| 341 | dep_name: []const u8, | |
| 342 | build_root: Cache.Directory, | |
| 340 | root: Cache.Path, | |
| 343 | 341 | pkg_hash: []const u8, |
| 344 | 342 | pkg_deps: AvailableDeps, |
| 345 | 343 | user_input_options: UserInputOptionsMap, |
| 346 | 344 | ) error{OutOfMemory}!*Build { |
| 347 | const allocator = parent.allocator; | |
| 348 | const child = try allocator.create(Build); | |
| 345 | const arena = parent.graph.arena; | |
| 346 | const child = try arena.create(Build); | |
| 349 | 347 | child.* = .{ |
| 350 | 348 | .graph = parent.graph, |
| 351 | .allocator = allocator, | |
| 349 | .root = root, | |
| 350 | .allocator = arena, | |
| 352 | 351 | .install_tls = .{ |
| 353 | 352 | .step = .init(.{ |
| 354 | .id = TopLevelStep.base_id, | |
| 353 | .tag = .top_level, | |
| 355 | 354 | .name = "install", |
| 356 | 355 | .owner = child, |
| 357 | 356 | }), |
| ... | ... | @@ -359,58 +358,31 @@ fn createChildOnly( |
| 359 | 358 | }, |
| 360 | 359 | .uninstall_tls = .{ |
| 361 | 360 | .step = .init(.{ |
| 362 | .id = TopLevelStep.base_id, | |
| 361 | .tag = .top_level, | |
| 363 | 362 | .name = "uninstall", |
| 364 | 363 | .owner = child, |
| 365 | .makeFn = makeUninstall, | |
| 366 | 364 | }), |
| 367 | 365 | .description = "Remove build artifacts from prefix path", |
| 368 | 366 | }, |
| 369 | 367 | .user_input_options = user_input_options, |
| 370 | .available_options_map = AvailableOptionsMap.init(allocator), | |
| 371 | .available_options_list = std.array_list.Managed(AvailableOption).init(allocator), | |
| 372 | .verbose = parent.verbose, | |
| 373 | .verbose_link = parent.verbose_link, | |
| 374 | .verbose_cc = parent.verbose_cc, | |
| 375 | .verbose_air = parent.verbose_air, | |
| 376 | .verbose_llvm_ir = parent.verbose_llvm_ir, | |
| 377 | .verbose_llvm_bc = parent.verbose_llvm_bc, | |
| 378 | .verbose_llvm_cpu_features = parent.verbose_llvm_cpu_features, | |
| 379 | .reference_trace = parent.reference_trace, | |
| 380 | 368 | .invalid_user_input = false, |
| 381 | 369 | .default_step = undefined, |
| 382 | 370 | .top_level_steps = .{}, |
| 383 | .install_prefix = undefined, | |
| 384 | .dest_dir = parent.dest_dir, | |
| 385 | .lib_dir = parent.lib_dir, | |
| 386 | .exe_dir = parent.exe_dir, | |
| 387 | .h_dir = parent.h_dir, | |
| 388 | .install_path = parent.install_path, | |
| 389 | .sysroot = parent.sysroot, | |
| 390 | .search_prefixes = parent.search_prefixes, | |
| 391 | .libc_file = parent.libc_file, | |
| 392 | .build_root = build_root, | |
| 393 | .cache_root = parent.cache_root, | |
| 394 | 371 | .debug_log_scopes = parent.debug_log_scopes, |
| 395 | .debug_compile_errors = parent.debug_compile_errors, | |
| 396 | .debug_incremental = parent.debug_incremental, | |
| 397 | .debug_pkg_config = parent.debug_pkg_config, | |
| 398 | 372 | .enable_darling = parent.enable_darling, |
| 399 | 373 | .enable_qemu = parent.enable_qemu, |
| 400 | 374 | .enable_rosetta = parent.enable_rosetta, |
| 401 | 375 | .enable_wasmtime = parent.enable_wasmtime, |
| 402 | 376 | .enable_wine = parent.enable_wine, |
| 403 | .libc_runtimes_dir = parent.libc_runtimes_dir, | |
| 404 | 377 | .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), |
| 405 | 378 | .modules = .empty, |
| 406 | 379 | .named_writefiles = .empty, |
| 407 | 380 | .named_lazy_paths = .empty, |
| 408 | 381 | .pkg_hash = pkg_hash, |
| 409 | 382 | .available_deps = pkg_deps, |
| 410 | .release_mode = parent.release_mode, | |
| 411 | 383 | }; |
| 412 | try child.top_level_steps.put(allocator, child.install_tls.step.name, &child.install_tls); | |
| 413 | try child.top_level_steps.put(allocator, child.uninstall_tls.step.name, &child.uninstall_tls); | |
| 384 | try child.top_level_steps.put(arena, child.install_tls.step.name, &child.install_tls); | |
| 385 | try child.top_level_steps.put(arena, child.uninstall_tls.step.name, &child.uninstall_tls); | |
| 414 | 386 | child.default_step = &child.install_tls.step; |
| 415 | 387 | return child; |
| 416 | 388 | } |
| ... | ... | @@ -624,13 +596,17 @@ const OrderedUserValue = union(enum) { |
| 624 | 596 | hasher.update(sp.sub_path); |
| 625 | 597 | }, |
| 626 | 598 | .generated => |gen| { |
| 627 | hasher.update(gen.file.step.owner.pkg_hash); | |
| 628 | hasher.update(std.mem.asBytes(&gen.up)); | |
| 599 | hasher.update(@ptrCast(&gen.index)); | |
| 600 | hasher.update(@ptrCast(&gen.up)); | |
| 629 | 601 | hasher.update(gen.sub_path); |
| 630 | 602 | }, |
| 631 | 603 | .cwd_relative => |rel_path| { |
| 632 | 604 | hasher.update(rel_path); |
| 633 | 605 | }, |
| 606 | .relative => |r| { | |
| 607 | hasher.update(@ptrCast(&r.base)); | |
| 608 | hasher.update(@ptrCast(&r.sub_path)); | |
| 609 | }, | |
| 634 | 610 | .dependency => |dep| { |
| 635 | 611 | hasher.update(dep.dependency.builder.pkg_hash); |
| 636 | 612 | hasher.update(dep.sub_path); |
| ... | ... | @@ -702,59 +678,6 @@ fn hashUserInputOptionsMap(allocator: Allocator, user_input_options: UserInputOp |
| 702 | 678 | user_option.hash(hasher); |
| 703 | 679 | } |
| 704 | 680 | |
| 705 | fn determineAndApplyInstallPrefix(b: *Build) error{OutOfMemory}!void { | |
| 706 | // Create an installation directory local to this package. This will be used when | |
| 707 | // dependant packages require a standard prefix, such as include directories for C headers. | |
| 708 | var hash = b.graph.cache.hash; | |
| 709 | // Random bytes to make unique. Refresh this with new random bytes when | |
| 710 | // implementation is modified in a non-backwards-compatible way. | |
| 711 | hash.add(@as(u32, 0xd8cb0055)); | |
| 712 | hash.addBytes(b.dep_prefix); | |
| 713 | ||
| 714 | var wyhash = std.hash.Wyhash.init(0); | |
| 715 | hashUserInputOptionsMap(b.allocator, b.user_input_options, &wyhash); | |
| 716 | hash.add(wyhash.final()); | |
| 717 | ||
| 718 | const digest = hash.final(); | |
| 719 | const install_prefix = try b.cache_root.join(b.allocator, &.{ "i", &digest }); | |
| 720 | b.resolveInstallPrefix(install_prefix, .{}); | |
| 721 | } | |
| 722 | ||
| 723 | /// This function is intended to be called by lib/build_runner.zig, not a build.zig file. | |
| 724 | pub fn resolveInstallPrefix(b: *Build, install_prefix: ?[]const u8, dir_list: DirList) void { | |
| 725 | if (b.dest_dir) |dest_dir| { | |
| 726 | b.install_prefix = install_prefix orelse "/usr"; | |
| 727 | b.install_path = b.pathJoin(&.{ dest_dir, b.install_prefix }); | |
| 728 | } else { | |
| 729 | b.install_prefix = install_prefix orelse | |
| 730 | (b.build_root.join(b.allocator, &.{"zig-out"}) catch @panic("unhandled error")); | |
| 731 | b.install_path = b.install_prefix; | |
| 732 | } | |
| 733 | ||
| 734 | var lib_list = [_][]const u8{ b.install_path, "lib" }; | |
| 735 | var exe_list = [_][]const u8{ b.install_path, "bin" }; | |
| 736 | var h_list = [_][]const u8{ b.install_path, "include" }; | |
| 737 | ||
| 738 | if (dir_list.lib_dir) |dir| { | |
| 739 | if (fs.path.isAbsolute(dir)) lib_list[0] = b.dest_dir orelse ""; | |
| 740 | lib_list[1] = dir; | |
| 741 | } | |
| 742 | ||
| 743 | if (dir_list.exe_dir) |dir| { | |
| 744 | if (fs.path.isAbsolute(dir)) exe_list[0] = b.dest_dir orelse ""; | |
| 745 | exe_list[1] = dir; | |
| 746 | } | |
| 747 | ||
| 748 | if (dir_list.include_dir) |dir| { | |
| 749 | if (fs.path.isAbsolute(dir)) h_list[0] = b.dest_dir orelse ""; | |
| 750 | h_list[1] = dir; | |
| 751 | } | |
| 752 | ||
| 753 | b.lib_dir = b.pathJoin(&lib_list); | |
| 754 | b.exe_dir = b.pathJoin(&exe_list); | |
| 755 | b.h_dir = b.pathJoin(&h_list); | |
| 756 | } | |
| 757 | ||
| 758 | 681 | /// Create a set of key-value pairs that can be converted into a Zig source |
| 759 | 682 | /// file and then inserted into a Zig compilation's module table for importing. |
| 760 | 683 | /// In other words, this provides a way to expose build.zig values to Zig |
| ... | ... | @@ -904,8 +827,10 @@ pub const AssemblyOptions = struct { |
| 904 | 827 | /// it available to other packages which depend on this one. |
| 905 | 828 | /// `createModule` can be used instead to create a private module. |
| 906 | 829 | pub fn addModule(b: *Build, name: []const u8, options: Module.CreateOptions) *Module { |
| 830 | const graph = b.graph; | |
| 831 | const arena = graph.arena; | |
| 907 | 832 | const module = Module.create(b, options); |
| 908 | b.modules.put(b.graph.arena, b.dupe(name), module) catch @panic("OOM"); | |
| 833 | b.modules.put(arena, graph.dupeString(name), module) catch @panic("OOM"); | |
| 909 | 834 | return module; |
| 910 | 835 | } |
| 911 | 836 | |
| ... | ... | @@ -916,11 +841,23 @@ pub fn createModule(b: *Build, options: Module.CreateOptions) *Module { |
| 916 | 841 | return Module.create(b, options); |
| 917 | 842 | } |
| 918 | 843 | |
| 919 | /// Initializes a `Step.Run` with argv, which must at least have the path to the | |
| 920 | /// executable. More command line arguments can be added with `addArg`, | |
| 921 | /// `addArgs`, and `addArtifactArg`. | |
| 922 | /// Be careful using this function, as it introduces a system dependency. | |
| 923 | /// To run an executable built with zig build, see `Step.Compile.run`. | |
| 844 | /// Creates a step that executes a process on the host system. | |
| 845 | /// | |
| 846 | /// `argv` is one or more command line arguments passed to the executed | |
| 847 | /// process. The first element is the name of the executable to run. More | |
| 848 | /// command line arguments can be added with methods of `Step.Run`, such as: | |
| 849 | /// * `Step.Run.addArgs` | |
| 850 | /// * `Step.Run.addArtifactArg` | |
| 851 | /// * `Step.Run.addFileArg` | |
| 852 | /// * `Step.Run.addOutputFileArg` | |
| 853 | /// | |
| 854 | /// This function introduces a system dependency, compromising reproducibility | |
| 855 | /// and making it more difficult to set up one's computer in order to build the | |
| 856 | /// project from source. | |
| 857 | /// | |
| 858 | /// See also: | |
| 859 | /// * `addRunArtifact` | |
| 860 | /// * `addRunFile` | |
| 924 | 861 | pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run { |
| 925 | 862 | assert(argv.len >= 1); |
| 926 | 863 | const run_step = Step.Run.create(b, b.fmt("run {s}", .{argv[0]})); |
| ... | ... | @@ -930,16 +867,22 @@ pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run { |
| 930 | 867 | |
| 931 | 868 | /// Creates a `Step.Run` with an executable built with `addExecutable`. |
| 932 | 869 | /// Add command line arguments with methods of `Step.Run`. |
| 870 | /// | |
| 871 | /// It doesn't have to target the host. In some cases cross-compiled binaries | |
| 872 | /// can even be executed. | |
| 873 | /// | |
| 874 | /// This is declarative; it constructs a build step that may or may not be run | |
| 875 | /// depending on the options provided by the user to the build command. | |
| 876 | /// | |
| 877 | /// See also: | |
| 878 | /// * `addSystemCommand` | |
| 879 | /// * `addRunFile` | |
| 933 | 880 | pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { |
| 934 | // It doesn't have to be native. We catch that if you actually try to run it. | |
| 935 | // Consider that this is declarative; the run step may not be run unless a user | |
| 936 | // option is supplied. | |
| 937 | ||
| 938 | 881 | // Avoid the common case of the step name looking like "run test test". |
| 939 | 882 | const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test")) |
| 940 | b.fmt("run {s}", .{@tagName(exe.kind)}) | |
| 883 | b.fmt("run {t}", .{exe.kind}) | |
| 941 | 884 | else |
| 942 | b.fmt("run {s} {s}", .{ @tagName(exe.kind), exe.name }); | |
| 885 | b.fmt("run {t} {s}", .{ exe.kind, exe.name }); | |
| 943 | 886 | |
| 944 | 887 | const run_step = Step.Run.create(b, step_name); |
| 945 | 888 | run_step.producer = exe; |
| ... | ... | @@ -994,6 +937,19 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { |
| 994 | 937 | return run_step; |
| 995 | 938 | } |
| 996 | 939 | |
| 940 | /// Creates a step that executes the provided file. | |
| 941 | /// | |
| 942 | /// Add more command line arguments via methods of `Step.Run`. | |
| 943 | /// | |
| 944 | /// See also: | |
| 945 | /// * `addSystemCommand` | |
| 946 | /// * `addRunArtifact` | |
| 947 | pub fn addRunFile(b: *Build, executable: LazyPath) *Step.Run { | |
| 948 | const run_step = Step.Run.create(b, b.fmt("run {f}", .{executable.fmt(b.graph)})); | |
| 949 | run_step.addFileArg(executable); | |
| 950 | return run_step; | |
| 951 | } | |
| 952 | ||
| 997 | 953 | /// Using the `values` provided, produces a C header file, possibly based on a |
| 998 | 954 | /// template input file (e.g. config.h.in). |
| 999 | 955 | /// When an input template file is provided, this function will fail the build |
| ... | ... | @@ -1013,36 +969,18 @@ pub fn addConfigHeader( |
| 1013 | 969 | return config_header_step; |
| 1014 | 970 | } |
| 1015 | 971 | |
| 1016 | /// Allocator.dupe without the need to handle out of memory. | |
| 1017 | pub fn dupe(b: *Build, bytes: []const u8) []u8 { | |
| 1018 | return dupeInner(b.allocator, bytes); | |
| 1019 | } | |
| 1020 | ||
| 1021 | pub fn dupeInner(allocator: std.mem.Allocator, bytes: []const u8) []u8 { | |
| 1022 | return allocator.dupe(u8, bytes) catch @panic("OOM"); | |
| 972 | pub fn dupe(b: *Build, bytes: []const u8) []const u8 { | |
| 973 | return b.graph.dupeString(bytes); | |
| 1023 | 974 | } |
| 1024 | 975 | |
| 1025 | /// Duplicates an array of strings without the need to handle out of memory. | |
| 1026 | pub fn dupeStrings(b: *Build, strings: []const []const u8) [][]u8 { | |
| 1027 | const array = b.allocator.alloc([]u8, strings.len) catch @panic("OOM"); | |
| 1028 | for (array, strings) |*dest, source| dest.* = b.dupe(source); | |
| 1029 | return array; | |
| 976 | /// Deprecated, call `Graph.dupeStrings` instead. | |
| 977 | pub fn dupeStrings(b: *Build, strings: []const []const u8) []const []const u8 { | |
| 978 | return b.graph.dupeStrings(strings); | |
| 1030 | 979 | } |
| 1031 | 980 | |
| 1032 | /// Duplicates a path and converts all slashes to the OS's canonical path separator. | |
| 1033 | pub fn dupePath(b: *Build, bytes: []const u8) []u8 { | |
| 1034 | return dupePathInner(b.allocator, bytes); | |
| 1035 | } | |
| 1036 | ||
| 1037 | fn dupePathInner(allocator: std.mem.Allocator, bytes: []const u8) []u8 { | |
| 1038 | const the_copy = dupeInner(allocator, bytes); | |
| 1039 | for (the_copy) |*byte| { | |
| 1040 | switch (byte.*) { | |
| 1041 | '/', '\\' => byte.* = fs.path.sep, | |
| 1042 | else => {}, | |
| 1043 | } | |
| 1044 | } | |
| 1045 | return the_copy; | |
| 981 | /// Deprecated, call `Graph.dupePath` instead. | |
| 982 | pub fn dupePath(b: *Build, bytes: []const u8) []const u8 { | |
| 983 | return b.graph.dupePath(bytes); | |
| 1046 | 984 | } |
| 1047 | 985 | |
| 1048 | 986 | pub fn addWriteFile(b: *Build, file_path: []const u8, data: []const u8) *Step.WriteFile { |
| ... | ... | @@ -1052,13 +990,15 @@ pub fn addWriteFile(b: *Build, file_path: []const u8, data: []const u8) *Step.Wr |
| 1052 | 990 | } |
| 1053 | 991 | |
| 1054 | 992 | pub fn addNamedWriteFiles(b: *Build, name: []const u8) *Step.WriteFile { |
| 993 | const graph = b.graph; | |
| 1055 | 994 | const wf = Step.WriteFile.create(b); |
| 1056 | b.named_writefiles.put(b.graph.arena, b.dupe(name), wf) catch @panic("OOM"); | |
| 995 | b.named_writefiles.put(graph.arena, graph.dupeString(name), wf) catch @panic("OOM"); | |
| 1057 | 996 | return wf; |
| 1058 | 997 | } |
| 1059 | 998 | |
| 1060 | 999 | pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void { |
| 1061 | b.named_lazy_paths.put(b.graph.arena, b.dupe(name), lp.dupe(b)) catch @panic("OOM"); | |
| 1000 | const graph = b.graph; | |
| 1001 | b.named_lazy_paths.put(graph.arena, graph.dupeString(name), lp.dupe(graph)) catch @panic("OOM"); | |
| 1062 | 1002 | } |
| 1063 | 1003 | |
| 1064 | 1004 | /// Creates a step for mutating files inside a temporary directory created lazily |
| ... | ... | @@ -1097,6 +1037,16 @@ pub fn addWriteFiles(b: *Build) *Step.WriteFile { |
| 1097 | 1037 | return Step.WriteFile.create(b); |
| 1098 | 1038 | } |
| 1099 | 1039 | |
| 1040 | /// Creates a step for writing data to paths relative to the build root, | |
| 1041 | /// mutating the project's source files. | |
| 1042 | /// | |
| 1043 | /// This build step was designed not to be used during the normal build | |
| 1044 | /// process, but rather as a utility run by a developer with intention to | |
| 1045 | /// update source files, which will then be committed to version control. | |
| 1046 | /// | |
| 1047 | /// Example use cases: | |
| 1048 | /// * precompiling assets which are tracked by version control | |
| 1049 | /// * snapshot testing | |
| 1100 | 1050 | pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles { |
| 1101 | 1051 | return Step.UpdateSourceFiles.create(b); |
| 1102 | 1052 | } |
| ... | ... | @@ -1121,28 +1071,21 @@ pub fn getUninstallStep(b: *Build) *Step { |
| 1121 | 1071 | return &b.uninstall_tls.step; |
| 1122 | 1072 | } |
| 1123 | 1073 | |
| 1124 | fn makeUninstall(uninstall_step: *Step, options: Step.MakeOptions) anyerror!void { | |
| 1125 | _ = options; | |
| 1126 | const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step); | |
| 1127 | const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls); | |
| 1128 | ||
| 1129 | _ = b; | |
| 1130 | @panic("TODO implement https://github.com/ziglang/zig/issues/14943"); | |
| 1131 | } | |
| 1132 | ||
| 1133 | 1074 | /// Creates a configuration option to be passed to the build.zig script. |
| 1134 | 1075 | /// When a user directly runs `zig build`, they can set these options with `-D` arguments. |
| 1135 | 1076 | /// When a project depends on a Zig package as a dependency, it programmatically sets |
| 1136 | 1077 | /// these options when calling the dependency's build.zig script as a function. |
| 1137 | 1078 | /// `null` is returned when an option is left to default. |
| 1138 | 1079 | pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T { |
| 1139 | const name = b.dupe(name_raw); | |
| 1140 | const description = b.dupe(description_raw); | |
| 1080 | const graph = b.graph; | |
| 1081 | const arena = graph.arena; | |
| 1082 | const name = graph.dupeString(name_raw); | |
| 1083 | const description = graph.dupeString(description_raw); | |
| 1141 | 1084 | const type_id = comptime typeToEnum(T); |
| 1142 | 1085 | const enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: { |
| 1143 | 1086 | const EnumType = if (type_id == .enum_list) @typeInfo(T).pointer.child else T; |
| 1144 | 1087 | const fields = comptime std.meta.fields(EnumType); |
| 1145 | var options = std.array_list.Managed([]const u8).initCapacity(b.allocator, fields.len) catch @panic("OOM"); | |
| 1088 | var options = std.array_list.Managed([]const u8).initCapacity(arena, fields.len) catch @panic("OOM"); | |
| 1146 | 1089 | |
| 1147 | 1090 | inline for (fields) |field| { |
| 1148 | 1091 | options.appendAssumeCapacity(field.name); |
| ... | ... | @@ -1156,10 +1099,9 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1156 | 1099 | .description = description, |
| 1157 | 1100 | .enum_options = enum_options, |
| 1158 | 1101 | }; |
| 1159 | if ((b.available_options_map.fetchPut(name, available_option) catch @panic("OOM")) != null) { | |
| 1160 | panic("Option '{s}' declared twice", .{name}); | |
| 1102 | if ((b.available_options_map.fetchPut(arena, name, available_option) catch @panic("OOM")) != null) { | |
| 1103 | panic("option '{s}' declared twice", .{name}); | |
| 1161 | 1104 | } |
| 1162 | b.available_options_list.append(available_option) catch @panic("OOM"); | |
| 1163 | 1105 | |
| 1164 | 1106 | const option_ptr = b.user_input_options.getPtr(name) orelse return null; |
| 1165 | 1107 | option_ptr.used = true; |
| ... | ... | @@ -1172,36 +1114,32 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1172 | 1114 | } else if (mem.eql(u8, s, "false")) { |
| 1173 | 1115 | return false; |
| 1174 | 1116 | } else { |
| 1175 | log.err("Expected -D{s} to be a boolean, but received '{s}'", .{ name, s }); | |
| 1117 | log.err("expected -D{s} to be a boolean; received: {s}", .{ name, s }); | |
| 1176 | 1118 | b.markInvalidUserInput(); |
| 1177 | 1119 | return null; |
| 1178 | 1120 | } |
| 1179 | 1121 | }, |
| 1180 | 1122 | .list, .map, .lazy_path, .lazy_path_list => { |
| 1181 | log.err("Expected -D{s} to be a boolean, but received a {s}.", .{ | |
| 1182 | name, @tagName(option_ptr.value), | |
| 1183 | }); | |
| 1123 | log.err("expected -D{s} to be a boolean; received: {t}", .{ name, option_ptr.value }); | |
| 1184 | 1124 | b.markInvalidUserInput(); |
| 1185 | 1125 | return null; |
| 1186 | 1126 | }, |
| 1187 | 1127 | }, |
| 1188 | 1128 | .int => switch (option_ptr.value) { |
| 1189 | 1129 | .flag, .list, .map, .lazy_path, .lazy_path_list => { |
| 1190 | log.err("Expected -D{s} to be an integer, but received a {s}.", .{ | |
| 1191 | name, @tagName(option_ptr.value), | |
| 1192 | }); | |
| 1130 | log.err("expected -D{s} to be an integer; received: {t}", .{ name, option_ptr.value }); | |
| 1193 | 1131 | b.markInvalidUserInput(); |
| 1194 | 1132 | return null; |
| 1195 | 1133 | }, |
| 1196 | 1134 | .scalar => |s| { |
| 1197 | 1135 | const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { |
| 1198 | 1136 | error.Overflow => { |
| 1199 | log.err("-D{s} value {s} cannot fit into type {s}.", .{ name, s, @typeName(T) }); | |
| 1137 | log.err("-D{s} value {s} cannot fit into type {s}", .{ name, s, @typeName(T) }); | |
| 1200 | 1138 | b.markInvalidUserInput(); |
| 1201 | 1139 | return null; |
| 1202 | 1140 | }, |
| 1203 | 1141 | else => { |
| 1204 | log.err("Expected -D{s} to be an integer of type {s}.", .{ name, @typeName(T) }); | |
| 1142 | log.err("expected -D{s} to be an integer of type {s}", .{ name, @typeName(T) }); | |
| 1205 | 1143 | b.markInvalidUserInput(); |
| 1206 | 1144 | return null; |
| 1207 | 1145 | }, |
| ... | ... | @@ -1211,15 +1149,13 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1211 | 1149 | }, |
| 1212 | 1150 | .float => switch (option_ptr.value) { |
| 1213 | 1151 | .flag, .map, .list, .lazy_path, .lazy_path_list => { |
| 1214 | log.err("Expected -D{s} to be a float, but received a {s}.", .{ | |
| 1215 | name, @tagName(option_ptr.value), | |
| 1216 | }); | |
| 1152 | log.err("expected -D{s} to be a float; received: {t}", .{ name, option_ptr.value }); | |
| 1217 | 1153 | b.markInvalidUserInput(); |
| 1218 | 1154 | return null; |
| 1219 | 1155 | }, |
| 1220 | 1156 | .scalar => |s| { |
| 1221 | 1157 | const n = std.fmt.parseFloat(T, s) catch { |
| 1222 | log.err("Expected -D{s} to be a float of type {s}.", .{ name, @typeName(T) }); | |
| 1158 | log.err("expected -D{s} to be a float of type {s}", .{ name, @typeName(T) }); | |
| 1223 | 1159 | b.markInvalidUserInput(); |
| 1224 | 1160 | return null; |
| 1225 | 1161 | }; |
| ... | ... | @@ -1228,9 +1164,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1228 | 1164 | }, |
| 1229 | 1165 | .@"enum" => switch (option_ptr.value) { |
| 1230 | 1166 | .flag, .map, .list, .lazy_path, .lazy_path_list => { |
| 1231 | log.err("Expected -D{s} to be an enum, but received a {s}.", .{ | |
| 1232 | name, @tagName(option_ptr.value), | |
| 1233 | }); | |
| 1167 | log.err("expected -D{s} to be an enum; received: {t}.", .{ name, option_ptr.value }); | |
| 1234 | 1168 | b.markInvalidUserInput(); |
| 1235 | 1169 | return null; |
| 1236 | 1170 | }, |
| ... | ... | @@ -1238,7 +1172,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1238 | 1172 | if (std.meta.stringToEnum(T, s)) |enum_lit| { |
| 1239 | 1173 | return enum_lit; |
| 1240 | 1174 | } else { |
| 1241 | log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(T) }); | |
| 1175 | log.err("expected -D{s} to be of type {s}", .{ name, @typeName(T) }); | |
| 1242 | 1176 | b.markInvalidUserInput(); |
| 1243 | 1177 | return null; |
| 1244 | 1178 | } |
| ... | ... | @@ -1246,9 +1180,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1246 | 1180 | }, |
| 1247 | 1181 | .string => switch (option_ptr.value) { |
| 1248 | 1182 | .flag, .list, .map, .lazy_path, .lazy_path_list => { |
| 1249 | log.err("Expected -D{s} to be a string, but received a {s}.", .{ | |
| 1250 | name, @tagName(option_ptr.value), | |
| 1251 | }); | |
| 1183 | log.err("expected -D{s} to be a string; received: {t}", .{ name, option_ptr.value }); | |
| 1252 | 1184 | b.markInvalidUserInput(); |
| 1253 | 1185 | return null; |
| 1254 | 1186 | }, |
| ... | ... | @@ -1256,9 +1188,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1256 | 1188 | }, |
| 1257 | 1189 | .build_id => switch (option_ptr.value) { |
| 1258 | 1190 | .flag, .map, .list, .lazy_path, .lazy_path_list => { |
| 1259 | log.err("Expected -D{s} to be an enum, but received a {s}.", .{ | |
| 1260 | name, @tagName(option_ptr.value), | |
| 1261 | }); | |
| 1191 | log.err("expected -D{s} to be an enum; received: {t}.", .{ name, option_ptr.value }); | |
| 1262 | 1192 | b.markInvalidUserInput(); |
| 1263 | 1193 | return null; |
| 1264 | 1194 | }, |
| ... | ... | @@ -1266,7 +1196,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1266 | 1196 | if (std.zig.BuildId.parse(s)) |build_id| { |
| 1267 | 1197 | return build_id; |
| 1268 | 1198 | } else |err| { |
| 1269 | log.err("unable to parse option '-D{s}': {s}", .{ name, @errorName(err) }); | |
| 1199 | log.err("failed to parse option -D{s}: {t}", .{ name, err }); | |
| 1270 | 1200 | b.markInvalidUserInput(); |
| 1271 | 1201 | return null; |
| 1272 | 1202 | } |
| ... | ... | @@ -1274,42 +1204,38 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1274 | 1204 | }, |
| 1275 | 1205 | .list => switch (option_ptr.value) { |
| 1276 | 1206 | .flag, .map, .lazy_path, .lazy_path_list => { |
| 1277 | log.err("Expected -D{s} to be a list, but received a {s}.", .{ | |
| 1278 | name, @tagName(option_ptr.value), | |
| 1279 | }); | |
| 1207 | log.err("expected -D{s} to be a list; received: {t}", .{ name, option_ptr.value }); | |
| 1280 | 1208 | b.markInvalidUserInput(); |
| 1281 | 1209 | return null; |
| 1282 | 1210 | }, |
| 1283 | 1211 | .scalar => |s| { |
| 1284 | return b.allocator.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM"); | |
| 1212 | return arena.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM"); | |
| 1285 | 1213 | }, |
| 1286 | 1214 | .list => |lst| return lst.items, |
| 1287 | 1215 | }, |
| 1288 | 1216 | .enum_list => switch (option_ptr.value) { |
| 1289 | 1217 | .flag, .map, .lazy_path, .lazy_path_list => { |
| 1290 | log.err("Expected -D{s} to be a list, but received a {s}.", .{ | |
| 1291 | name, @tagName(option_ptr.value), | |
| 1292 | }); | |
| 1218 | log.err("expected -D{s} to be a list; received: {t}", .{ name, option_ptr.value }); | |
| 1293 | 1219 | b.markInvalidUserInput(); |
| 1294 | 1220 | return null; |
| 1295 | 1221 | }, |
| 1296 | 1222 | .scalar => |s| { |
| 1297 | 1223 | const Child = @typeInfo(T).pointer.child; |
| 1298 | 1224 | const value = std.meta.stringToEnum(Child, s) orelse { |
| 1299 | log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(Child) }); | |
| 1225 | log.err("expected -D{s} to be of type {s}", .{ name, @typeName(Child) }); | |
| 1300 | 1226 | b.markInvalidUserInput(); |
| 1301 | 1227 | return null; |
| 1302 | 1228 | }; |
| 1303 | return b.allocator.dupe(Child, &[_]Child{value}) catch @panic("OOM"); | |
| 1229 | return arena.dupe(Child, &[_]Child{value}) catch @panic("OOM"); | |
| 1304 | 1230 | }, |
| 1305 | 1231 | .list => |lst| { |
| 1306 | 1232 | const Child = @typeInfo(T).pointer.child; |
| 1307 | const new_list = b.allocator.alloc(Child, lst.items.len) catch @panic("OOM"); | |
| 1233 | const new_list = graph.alloc(Child, lst.items.len); | |
| 1308 | 1234 | for (new_list, lst.items) |*new_item, str| { |
| 1309 | 1235 | new_item.* = std.meta.stringToEnum(Child, str) orelse { |
| 1310 | log.err("Expected -D{s} to be of type {s}.", .{ name, @typeName(Child) }); | |
| 1236 | log.err("expected -D{s} to be of type {s}", .{ name, @typeName(Child) }); | |
| 1311 | 1237 | b.markInvalidUserInput(); |
| 1312 | b.allocator.free(new_list); | |
| 1238 | arena.free(new_list); | |
| 1313 | 1239 | return null; |
| 1314 | 1240 | }; |
| 1315 | 1241 | } |
| ... | ... | @@ -1320,18 +1246,16 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1320 | 1246 | .scalar => |s| return .{ .cwd_relative = s }, |
| 1321 | 1247 | .lazy_path => |lp| return lp, |
| 1322 | 1248 | .flag, .map, .list, .lazy_path_list => { |
| 1323 | log.err("Expected -D{s} to be a path, but received a {s}.", .{ | |
| 1324 | name, @tagName(option_ptr.value), | |
| 1325 | }); | |
| 1249 | log.err("expected -D{s} to be a path; received: {t}", .{ name, option_ptr.value }); | |
| 1326 | 1250 | b.markInvalidUserInput(); |
| 1327 | 1251 | return null; |
| 1328 | 1252 | }, |
| 1329 | 1253 | }, |
| 1330 | 1254 | .lazy_path_list => switch (option_ptr.value) { |
| 1331 | .scalar => |s| return b.allocator.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"), | |
| 1332 | .lazy_path => |lp| return b.allocator.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"), | |
| 1255 | .scalar => |s| return arena.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"), | |
| 1256 | .lazy_path => |lp| return arena.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"), | |
| 1333 | 1257 | .list => |lst| { |
| 1334 | const new_list = b.allocator.alloc(LazyPath, lst.items.len) catch @panic("OOM"); | |
| 1258 | const new_list = graph.alloc(LazyPath, lst.items.len); | |
| 1335 | 1259 | for (new_list, lst.items) |*new_item, str| { |
| 1336 | 1260 | new_item.* = .{ .cwd_relative = str }; |
| 1337 | 1261 | } |
| ... | ... | @@ -1339,9 +1263,7 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1339 | 1263 | }, |
| 1340 | 1264 | .lazy_path_list => |lp_list| return lp_list.items, |
| 1341 | 1265 | .flag, .map => { |
| 1342 | log.err("Expected -D{s} to be a path, but received a {s}.", .{ | |
| 1343 | name, @tagName(option_ptr.value), | |
| 1344 | }); | |
| 1266 | log.err("expected -D{s} to be a path; received: {t}", .{ name, option_ptr.value }); | |
| 1345 | 1267 | b.markInvalidUserInput(); |
| 1346 | 1268 | return null; |
| 1347 | 1269 | }, |
| ... | ... | @@ -1350,17 +1272,19 @@ pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw |
| 1350 | 1272 | } |
| 1351 | 1273 | |
| 1352 | 1274 | pub fn step(b: *Build, name: []const u8, description: []const u8) *Step { |
| 1353 | const step_info = b.allocator.create(TopLevelStep) catch @panic("OOM"); | |
| 1275 | const graph = b.graph; | |
| 1276 | const arena = graph.arena; | |
| 1277 | const step_info = arena.create(Step.TopLevel) catch @panic("OOM"); | |
| 1354 | 1278 | step_info.* = .{ |
| 1355 | 1279 | .step = .init(.{ |
| 1356 | .id = TopLevelStep.base_id, | |
| 1280 | .tag = .top_level, | |
| 1357 | 1281 | .name = name, |
| 1358 | 1282 | .owner = b, |
| 1359 | 1283 | }), |
| 1360 | .description = b.dupe(description), | |
| 1284 | .description = graph.dupeString(description), | |
| 1361 | 1285 | }; |
| 1362 | const gop = b.top_level_steps.getOrPut(b.allocator, name) catch @panic("OOM"); | |
| 1363 | if (gop.found_existing) std.debug.panic("A top-level step with name \"{s}\" already exists", .{name}); | |
| 1286 | const gop = b.top_level_steps.getOrPut(arena, name) catch @panic("OOM"); | |
| 1287 | if (gop.found_existing) panic("A top-level step with name \"{s}\" already exists", .{name}); | |
| 1364 | 1288 | |
| 1365 | 1289 | gop.key_ptr.* = step_info.step.name; |
| 1366 | 1290 | gop.value_ptr.* = step_info; |
| ... | ... | @@ -1373,8 +1297,10 @@ pub const StandardOptimizeOptionOptions = struct { |
| 1373 | 1297 | }; |
| 1374 | 1298 | |
| 1375 | 1299 | pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions) std.builtin.OptimizeMode { |
| 1300 | const graph = b.graph; | |
| 1301 | ||
| 1376 | 1302 | if (options.preferred_optimize_mode) |mode| { |
| 1377 | if (b.option(bool, "release", "optimize for end users") orelse (b.release_mode != .off)) { | |
| 1303 | if (b.option(bool, "release", "optimize for end users") orelse (graph.release_mode != .off)) { | |
| 1378 | 1304 | return mode; |
| 1379 | 1305 | } else { |
| 1380 | 1306 | return .Debug; |
| ... | ... | @@ -1389,7 +1315,7 @@ pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions) |
| 1389 | 1315 | return mode; |
| 1390 | 1316 | } |
| 1391 | 1317 | |
| 1392 | return switch (b.release_mode) { | |
| 1318 | return switch (graph.release_mode) { | |
| 1393 | 1319 | .off => .Debug, |
| 1394 | 1320 | .any => { |
| 1395 | 1321 | std.debug.print("the project does not declare a preferred optimization mode. choose: --release=fast, --release=safe, or --release=small\n", .{}); |
| ... | ... | @@ -1424,8 +1350,8 @@ pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFaile |
| 1424 | 1350 | opts_copy.diagnostics = &diags; |
| 1425 | 1351 | return std.Target.Query.parse(opts_copy) catch |err| switch (err) { |
| 1426 | 1352 | error.UnknownCpuModel => { |
| 1427 | std.debug.print("unknown CPU: '{s}'\navailable CPUs for architecture '{s}':\n", .{ | |
| 1428 | diags.cpu_name.?, @tagName(diags.arch.?), | |
| 1353 | std.debug.print("unknown CPU: '{s}'\navailable CPUs for architecture '{t}':\n", .{ | |
| 1354 | diags.cpu_name.?, diags.arch.?, | |
| 1429 | 1355 | }); |
| 1430 | 1356 | for (diags.arch.?.allCpuModels()) |cpu| { |
| 1431 | 1357 | std.debug.print(" {s}\n", .{cpu.name}); |
| ... | ... | @@ -1435,11 +1361,10 @@ pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFaile |
| 1435 | 1361 | error.UnknownCpuFeature => { |
| 1436 | 1362 | std.debug.print( |
| 1437 | 1363 | \\unknown CPU feature: '{s}' |
| 1438 | \\available CPU features for architecture '{s}': | |
| 1364 | \\available CPU features for architecture '{t}': | |
| 1439 | 1365 | \\ |
| 1440 | 1366 | , .{ |
| 1441 | diags.unknown_feature_name.?, | |
| 1442 | @tagName(diags.arch.?), | |
| 1367 | diags.unknown_feature_name.?, diags.arch.?, | |
| 1443 | 1368 | }); |
| 1444 | 1369 | for (diags.arch.?.allFeaturesList()) |feature| { |
| 1445 | 1370 | std.debug.print(" {s}: {s}\n", .{ feature.name, feature.description }); |
| ... | ... | @@ -1468,6 +1393,9 @@ pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFaile |
| 1468 | 1393 | |
| 1469 | 1394 | /// Exposes standard `zig build` options for choosing a target. |
| 1470 | 1395 | pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs) Target.Query { |
| 1396 | const graph = b.graph; | |
| 1397 | const arena = graph.arena; | |
| 1398 | ||
| 1471 | 1399 | const maybe_triple = b.option( |
| 1472 | 1400 | []const u8, |
| 1473 | 1401 | "target", |
| ... | ... | @@ -1516,20 +1444,22 @@ pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs |
| 1516 | 1444 | |
| 1517 | 1445 | for (whitelist) |q| { |
| 1518 | 1446 | log.info("allowed target: -Dtarget={s} -Dcpu={s}", .{ |
| 1519 | q.zigTriple(b.allocator) catch @panic("OOM"), | |
| 1520 | q.serializeCpuAlloc(b.allocator) catch @panic("OOM"), | |
| 1447 | q.zigTriple(arena) catch @panic("OOM"), | |
| 1448 | q.serializeCpuAlloc(arena) catch @panic("OOM"), | |
| 1521 | 1449 | }); |
| 1522 | 1450 | } |
| 1523 | 1451 | log.err("chosen target '{s}' does not match one of the allowed targets", .{ |
| 1524 | selected_target.zigTriple(b.allocator) catch @panic("OOM"), | |
| 1452 | selected_target.zigTriple(arena) catch @panic("OOM"), | |
| 1525 | 1453 | }); |
| 1526 | 1454 | b.markInvalidUserInput(); |
| 1527 | 1455 | return args.default_target; |
| 1528 | 1456 | } |
| 1529 | 1457 | |
| 1530 | 1458 | pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8) error{OutOfMemory}!bool { |
| 1531 | const name = b.dupe(name_raw); | |
| 1532 | const value = b.dupe(value_raw); | |
| 1459 | const graph = b.graph; | |
| 1460 | const arena = graph.arena; | |
| 1461 | const name = graph.dupeString(name_raw); | |
| 1462 | const value = graph.dupeString(value_raw); | |
| 1533 | 1463 | const gop = try b.user_input_options.getOrPut(name); |
| 1534 | 1464 | if (!gop.found_existing) { |
| 1535 | 1465 | gop.value_ptr.* = UserInputOption{ |
| ... | ... | @@ -1544,7 +1474,7 @@ pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8 |
| 1544 | 1474 | switch (gop.value_ptr.value) { |
| 1545 | 1475 | .scalar => |s| { |
| 1546 | 1476 | // turn it into a list |
| 1547 | var list = std.array_list.Managed([]const u8).init(b.allocator); | |
| 1477 | var list = std.array_list.Managed([]const u8).init(arena); | |
| 1548 | 1478 | try list.append(s); |
| 1549 | 1479 | try list.append(value); |
| 1550 | 1480 | try b.user_input_options.put(name, .{ |
| ... | ... | @@ -1572,7 +1502,9 @@ pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8 |
| 1572 | 1502 | return true; |
| 1573 | 1503 | }, |
| 1574 | 1504 | .lazy_path, .lazy_path_list => { |
| 1575 | log.warn("the lazy path value type isn't added from the CLI, but somehow '{s}' is a .{f}", .{ name, std.zig.fmtId(@tagName(gop.value_ptr.value)) }); | |
| 1505 | log.warn("the lazy path value type isn't added from the CLI, but somehow '{s}' is a .{f}", .{ | |
| 1506 | name, std.zig.fmtId(@tagName(gop.value_ptr.value)), | |
| 1507 | }); | |
| 1576 | 1508 | return true; |
| 1577 | 1509 | }, |
| 1578 | 1510 | } |
| ... | ... | @@ -1580,7 +1512,8 @@ pub fn addUserInputOption(b: *Build, name_raw: []const u8, value_raw: []const u8 |
| 1580 | 1512 | } |
| 1581 | 1513 | |
| 1582 | 1514 | pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool { |
| 1583 | const name = b.dupe(name_raw); | |
| 1515 | const graph = b.graph; | |
| 1516 | const name = graph.dupeString(name_raw); | |
| 1584 | 1517 | const gop = try b.user_input_options.getOrPut(name); |
| 1585 | 1518 | if (!gop.found_existing) { |
| 1586 | 1519 | gop.value_ptr.* = .{ |
| ... | ... | @@ -1602,7 +1535,7 @@ pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool |
| 1602 | 1535 | return true; |
| 1603 | 1536 | }, |
| 1604 | 1537 | .lazy_path => |lp| { |
| 1605 | log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, lp.getDisplayName() }); | |
| 1538 | log.err("Flag '-D{s}' conflicts with option '-D{s}={f}'.", .{ name, name, lp }); | |
| 1606 | 1539 | return true; |
| 1607 | 1540 | }, |
| 1608 | 1541 | |
| ... | ... | @@ -1611,7 +1544,7 @@ pub fn addUserInputFlag(b: *Build, name_raw: []const u8) error{OutOfMemory}!bool |
| 1611 | 1544 | return false; |
| 1612 | 1545 | } |
| 1613 | 1546 | |
| 1614 | fn typeToEnum(comptime T: type) TypeId { | |
| 1547 | fn typeToEnum(comptime T: type) Configuration.AvailableOption.Type { | |
| 1615 | 1548 | return switch (T) { |
| 1616 | 1549 | std.zig.BuildId => .build_id, |
| 1617 | 1550 | LazyPath => .lazy_path, |
| ... | ... | @@ -1732,26 +1665,10 @@ pub fn addCheckFile( |
| 1732 | 1665 | return Step.CheckFile.create(b, file_source, options); |
| 1733 | 1666 | } |
| 1734 | 1667 | |
| 1735 | pub fn truncateFile(b: *Build, dest_path: []const u8) (Io.Dir.CreateDirError || Io.Dir.StatFileError)!void { | |
| 1736 | const io = b.graph.io; | |
| 1737 | if (b.verbose) log.info("truncate {s}", .{dest_path}); | |
| 1738 | const cwd = Io.Dir.cwd(); | |
| 1739 | var src_file = cwd.createFile(io, dest_path, .{}) catch |err| switch (err) { | |
| 1740 | error.FileNotFound => blk: { | |
| 1741 | if (fs.path.dirname(dest_path)) |dirname| { | |
| 1742 | try cwd.createDirPath(io, dirname); | |
| 1743 | } | |
| 1744 | break :blk try cwd.createFile(io, dest_path, .{}); | |
| 1745 | }, | |
| 1746 | else => |e| return e, | |
| 1747 | }; | |
| 1748 | src_file.close(io); | |
| 1749 | } | |
| 1750 | ||
| 1751 | 1668 | /// References a file or directory relative to the source root. |
| 1752 | 1669 | pub fn path(b: *Build, sub_path: []const u8) LazyPath { |
| 1753 | 1670 | if (fs.path.isAbsolute(sub_path)) { |
| 1754 | std.debug.panic("sub_path is expected to be relative to the build root, but was this absolute path: '{s}'. It is best avoid absolute paths, but if you must, it is supported by LazyPath.cwd_relative", .{ | |
| 1671 | panic("sub_path is expected to be relative to the build root, but was this absolute path: '{s}'. Absolute paths can cause problems but can be created via Graph.cwdRelativePath", .{ | |
| 1755 | 1672 | sub_path, |
| 1756 | 1673 | }); |
| 1757 | 1674 | } |
| ... | ... | @@ -1761,27 +1678,106 @@ pub fn path(b: *Build, sub_path: []const u8) LazyPath { |
| 1761 | 1678 | } }; |
| 1762 | 1679 | } |
| 1763 | 1680 | |
| 1764 | /// This is low-level implementation details of the build system, not meant to | |
| 1765 | /// be called by users' build scripts. Even in the build system itself it is a | |
| 1766 | /// code smell to call this function. | |
| 1767 | pub fn pathFromRoot(b: *Build, sub_path: []const u8) []u8 { | |
| 1768 | return b.pathResolve(&.{ b.build_root.path orelse ".", sub_path }); | |
| 1769 | } | |
| 1770 | ||
| 1771 | fn pathFromCwd(b: *Build, sub_path: []const u8) []u8 { | |
| 1772 | return b.pathResolve(&.{ b.graph.cache.cwd, sub_path }); | |
| 1681 | /// Creates a list of files and/or directories relative to the source root. | |
| 1682 | pub fn pathList(b: *Build, sub_paths: []const []const u8) []const LazyPath { | |
| 1683 | const graph = b.graph; | |
| 1684 | const result = graph.alloc(LazyPath, sub_paths.len); | |
| 1685 | for (result, sub_paths) |*d, s| d.* = path(b, s); | |
| 1686 | return result; | |
| 1773 | 1687 | } |
| 1774 | 1688 | |
| 1775 | 1689 | pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 { |
| 1776 | return fs.path.join(b.allocator, paths) catch @panic("OOM"); | |
| 1690 | const graph = b.graph; | |
| 1691 | const arena = graph.arena; | |
| 1692 | return fs.path.join(arena, paths) catch @panic("OOM"); | |
| 1777 | 1693 | } |
| 1778 | 1694 | |
| 1779 | 1695 | pub fn pathResolve(b: *Build, paths: []const []const u8) []u8 { |
| 1780 | return fs.path.resolve(b.allocator, paths) catch @panic("OOM"); | |
| 1696 | const graph = b.graph; | |
| 1697 | const arena = graph.arena; | |
| 1698 | return fs.path.resolve(arena, paths) catch @panic("OOM"); | |
| 1781 | 1699 | } |
| 1782 | 1700 | |
| 1783 | 1701 | pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { |
| 1784 | return std.fmt.allocPrint(b.allocator, format, args) catch @panic("OOM"); | |
| 1702 | const graph = b.graph; | |
| 1703 | const arena = graph.arena; | |
| 1704 | return std.fmt.allocPrint(arena, format, args) catch @panic("OOM"); | |
| 1705 | } | |
| 1706 | ||
| 1707 | /// Creates an anonymous `Step` that searches for an executable on the host that | |
| 1708 | /// has more than one possible name. | |
| 1709 | /// | |
| 1710 | /// Returns the `LazyPath` of the found executable. The search only takes place | |
| 1711 | /// if the `LazyPath` will be used by a depending `Step`. | |
| 1712 | /// | |
| 1713 | /// This API is useful in the following cases: | |
| 1714 | /// * The binary is not named the same across all systems (for example "python" | |
| 1715 | /// vs "python3"). | |
| 1716 | /// * The binary may be produced by building from source rather than being | |
| 1717 | /// globally installed and will therefore be possibly found in one of the | |
| 1718 | /// search prefix paths. | |
| 1719 | /// | |
| 1720 | /// Names are searched in order, observing search prefixes first and then PATH | |
| 1721 | /// environment variable. | |
| 1722 | /// | |
| 1723 | /// Windows file name extensions are searched automatically, respecting the | |
| 1724 | /// PATHEXT environment variable, so they need not be included in this list. | |
| 1725 | /// However, even on Windows, the names will be checked without appending | |
| 1726 | /// extensions first, so that can be used as a priority system. | |
| 1727 | /// | |
| 1728 | /// See also: | |
| 1729 | /// * `findProgram` | |
| 1730 | pub fn findProgramLazy(b: *Build, options: Step.FindProgram.Options) LazyPath { | |
| 1731 | return .{ .generated = .{ .index = Step.FindProgram.create(b, options).found_path } }; | |
| 1732 | } | |
| 1733 | ||
| 1734 | pub const FindProgramOptions = Step.FindProgram.Options; | |
| 1735 | ||
| 1736 | /// Immediately (in the configure phase), searches for an executable on the host | |
| 1737 | /// that has more than one possible name. | |
| 1738 | /// | |
| 1739 | /// Calling this function poisons the configuration cache, so it is only | |
| 1740 | /// appropriate when the existence of the program or its output needs to be | |
| 1741 | /// observed by configuration logic. For more information, see | |
| 1742 | /// `Graph.CachePoison` documentation. | |
| 1743 | /// | |
| 1744 | /// Names are searched in order, observing search prefixes first and then PATH | |
| 1745 | /// environment variable. | |
| 1746 | /// | |
| 1747 | /// Windows file name extensions are searched automatically, respecting the | |
| 1748 | /// PATHEXT environment variable, so they need not be included in this list. | |
| 1749 | /// However, even on Windows, the names will be checked without appending | |
| 1750 | /// extensions first, so that can be used as a priority system. | |
| 1751 | /// | |
| 1752 | /// See also: | |
| 1753 | /// * `findProgramLazy` | |
| 1754 | pub fn findProgram(b: *Build, options: FindProgramOptions) ?[]const u8 { | |
| 1755 | const graph = b.graph; | |
| 1756 | ||
| 1757 | // Because it observes search prefixes and contents of directories in PATH. | |
| 1758 | graph.poisonCache(); | |
| 1759 | ||
| 1760 | for (options.names) |name| { | |
| 1761 | if (Io.Dir.path.isAbsolute(name)) { | |
| 1762 | if (tryFindProgram(b, name)) |found| return found; | |
| 1763 | } | |
| 1764 | for (graph.search_prefixes.items) |search_prefix| { | |
| 1765 | const full_path = b.pathJoin(&.{ search_prefix, "bin", name }); | |
| 1766 | if (tryFindProgram(b, full_path)) |found| return found; | |
| 1767 | } | |
| 1768 | } | |
| 1769 | ||
| 1770 | if (b.graph.environ_map.get("PATH")) |PATH| { | |
| 1771 | for (options.names) |name| { | |
| 1772 | var it = mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter); | |
| 1773 | while (it.next()) |p| { | |
| 1774 | const full_path = b.pathJoin(&.{ p, name }); | |
| 1775 | if (tryFindProgram(b, full_path)) |found| return found; | |
| 1776 | } | |
| 1777 | } | |
| 1778 | } | |
| 1779 | ||
| 1780 | return null; | |
| 1785 | 1781 | } |
| 1786 | 1782 | |
| 1787 | 1783 | fn supportedWindowsProgramExtension(ext: []const u8) bool { |
| ... | ... | @@ -1792,14 +1788,17 @@ fn supportedWindowsProgramExtension(ext: []const u8) bool { |
| 1792 | 1788 | } |
| 1793 | 1789 | |
| 1794 | 1790 | fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { |
| 1795 | const io = b.graph.io; | |
| 1796 | const arena = b.allocator; | |
| 1791 | const graph = b.graph; | |
| 1792 | const io = graph.io; | |
| 1793 | const arena = graph.arena; | |
| 1797 | 1794 | |
| 1798 | if (b.build_root.handle.realPathFileAlloc(io, full_path, arena)) |p| { | |
| 1799 | return p; | |
| 1795 | if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| { | |
| 1796 | return full_path; | |
| 1800 | 1797 | } else |err| switch (err) { |
| 1801 | error.OutOfMemory => @panic("OOM"), | |
| 1802 | else => {}, | |
| 1798 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { | |
| 1799 | if (graph.verbose) log.info("searched: {t} {s}", .{ e, full_path }); | |
| 1800 | }, | |
| 1801 | else => |e| return panic("failed accessing {s}: {t}", .{ full_path, e }), | |
| 1803 | 1802 | } |
| 1804 | 1803 | |
| 1805 | 1804 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -1809,14 +1808,16 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { |
| 1809 | 1808 | while (it.next()) |ext| { |
| 1810 | 1809 | if (!supportedWindowsProgramExtension(ext)) continue; |
| 1811 | 1810 | |
| 1812 | return b.build_root.handle.realPathFileAlloc( | |
| 1813 | io, | |
| 1814 | b.fmt("{s}{s}", .{ full_path, ext }), | |
| 1815 | arena, | |
| 1816 | ) catch |err| switch (err) { | |
| 1817 | error.OutOfMemory => @panic("OOM"), | |
| 1818 | else => continue, | |
| 1819 | }; | |
| 1811 | const extended_path = try mem.concat(arena, &.{ full_path, ext }); | |
| 1812 | ||
| 1813 | if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| { | |
| 1814 | return extended_path; | |
| 1815 | } else |err| switch (err) { | |
| 1816 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { | |
| 1817 | if (graph.verbose) log.info("searched: {t} {s}", .{ e, extended_path }); | |
| 1818 | }, | |
| 1819 | else => |e| return panic("failed accessing {s}: {t}", .{ extended_path, e }), | |
| 1820 | } | |
| 1820 | 1821 | } |
| 1821 | 1822 | } |
| 1822 | 1823 | } |
| ... | ... | @@ -1824,114 +1825,158 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { |
| 1824 | 1825 | return null; |
| 1825 | 1826 | } |
| 1826 | 1827 | |
| 1827 | pub fn findProgram(b: *Build, names: []const []const u8, paths: []const []const u8) error{FileNotFound}![]const u8 { | |
| 1828 | // TODO report error for ambiguous situations | |
| 1829 | for (b.search_prefixes.items) |search_prefix| { | |
| 1830 | for (names) |name| { | |
| 1831 | if (fs.path.isAbsolute(name)) { | |
| 1832 | return name; | |
| 1833 | } | |
| 1834 | return tryFindProgram(b, b.pathJoin(&.{ search_prefix, "bin", name })) orelse continue; | |
| 1835 | } | |
| 1836 | } | |
| 1837 | if (b.graph.environ_map.get("PATH")) |PATH| { | |
| 1838 | for (names) |name| { | |
| 1839 | if (fs.path.isAbsolute(name)) { | |
| 1840 | return name; | |
| 1841 | } | |
| 1842 | var it = mem.tokenizeScalar(u8, PATH, fs.path.delimiter); | |
| 1843 | while (it.next()) |p| { | |
| 1844 | return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue; | |
| 1845 | } | |
| 1846 | } | |
| 1847 | } | |
| 1848 | for (names) |name| { | |
| 1849 | if (fs.path.isAbsolute(name)) { | |
| 1850 | return name; | |
| 1851 | } | |
| 1852 | for (paths) |p| { | |
| 1853 | return tryFindProgram(b, b.pathJoin(&.{ p, name })) orelse continue; | |
| 1854 | } | |
| 1855 | } | |
| 1856 | return error.FileNotFound; | |
| 1857 | } | |
| 1858 | ||
| 1828 | /// Deprecated; use `runFallible`. | |
| 1859 | 1829 | pub fn runAllowFail( |
| 1860 | 1830 | b: *Build, |
| 1861 | 1831 | argv: []const []const u8, |
| 1862 | out_code: *u8, | |
| 1863 | stderr_behavior: std.process.SpawnOptions.StdIo, | |
| 1864 | ) RunError![]u8 { | |
| 1865 | assert(argv.len != 0); | |
| 1832 | exit_code: *u8, | |
| 1833 | stderr_behavior: process.SpawnOptions.StdIo, | |
| 1834 | ) anyerror![]u8 { | |
| 1835 | if (!process.can_spawn) return error.ExecNotSupported; | |
| 1836 | switch (runFallible(b, argv, .{ | |
| 1837 | .stderr_behavior = stderr_behavior, | |
| 1838 | })) { | |
| 1839 | .success => |stdout| return stdout, | |
| 1840 | .spawn_failed => |err| return err, | |
| 1841 | .bad_exit_code => |code| { | |
| 1842 | exit_code.* = code; | |
| 1843 | return error.ExitCodeFailure; | |
| 1844 | }, | |
| 1845 | .crashed => { | |
| 1846 | exit_code.* = 255; | |
| 1847 | return error.ProcessTerminated; | |
| 1848 | }, | |
| 1849 | } | |
| 1850 | } | |
| 1851 | ||
| 1852 | pub const RunOptions = struct { | |
| 1853 | stderr_behavior: process.SpawnOptions.StdIo = .inherit, | |
| 1854 | /// Fail the configuration if stdout is larger than this. | |
| 1855 | stdout_limit: Io.Limit = .limited(1_000_000), | |
| 1856 | /// Set to change the current working directory when spawning the child | |
| 1857 | /// process. | |
| 1858 | cwd: process.Child.Cwd = .inherit, | |
| 1859 | /// Replaces the child environment when provided. The PATH value from here | |
| 1860 | /// is not used to resolve `argv[0]`; that resolution always uses parent | |
| 1861 | /// environment. | |
| 1862 | environ_map: ?*const process.Environ.Map = null, | |
| 1863 | expand_arg0: process.ArgExpansion = .no_expand, | |
| 1864 | }; | |
| 1865 | ||
| 1866 | pub const RunResult = union(enum) { | |
| 1867 | /// Thild process exited with code 0, writing this stdout. | |
| 1868 | success: []u8, | |
| 1869 | /// The child process could not be created. | |
| 1870 | spawn_failed: process.SpawnError, | |
| 1871 | /// The child process indicated failure. | |
| 1872 | bad_exit_code: u8, | |
| 1873 | /// The child process terminated abnormally. | |
| 1874 | crashed, | |
| 1875 | }; | |
| 1866 | 1876 | |
| 1867 | if (!process.can_spawn) | |
| 1868 | return error.ExecNotSupported; | |
| 1877 | /// Executes the provided command immediately, allowing failure. | |
| 1878 | /// | |
| 1879 | /// If the program exits successfully, stdout is returned. Otherwise, returns | |
| 1880 | /// an indication of failure. | |
| 1881 | /// | |
| 1882 | /// See also: | |
| 1883 | /// * `run`. | |
| 1884 | pub fn runFallible(b: *Build, argv: []const []const u8, options: RunOptions) RunResult { | |
| 1885 | assert(argv.len != 0); | |
| 1869 | 1886 | |
| 1870 | 1887 | const graph = b.graph; |
| 1871 | 1888 | const io = graph.io; |
| 1889 | const arena = graph.arena; | |
| 1890 | ||
| 1891 | const print_opts: std.zig.AllocPrintCmdOptions = .{ | |
| 1892 | .cwd = switch (options.cwd) { | |
| 1893 | .inherit => null, | |
| 1894 | .path => |p| p, | |
| 1895 | .dir => null, // Unknown without changing function signature of runFallible. | |
| 1896 | }, | |
| 1897 | .child_env = options.environ_map, | |
| 1898 | .parent_env = &graph.environ_map, | |
| 1899 | }; | |
| 1872 | 1900 | |
| 1873 | const max_output_size = 400 * 1024; | |
| 1874 | try Step.handleVerbose2(b, .inherit, &graph.environ_map, argv); | |
| 1901 | if (graph.verbose) { | |
| 1902 | const text = std.zig.allocPrintCmd(arena, argv, print_opts) catch @panic("OOM"); | |
| 1903 | std.log.scoped(.verbose).info("{s}", .{text}); | |
| 1904 | } | |
| 1875 | 1905 | |
| 1876 | var child = try std.process.spawn(io, .{ | |
| 1906 | var child = process.spawn(io, .{ | |
| 1877 | 1907 | .argv = argv, |
| 1878 | .environ_map = &graph.environ_map, | |
| 1879 | 1908 | .stdin = .ignore, |
| 1880 | 1909 | .stdout = .pipe, |
| 1881 | .stderr = stderr_behavior, | |
| 1882 | }); | |
| 1910 | .stderr = options.stderr_behavior, | |
| 1911 | .cwd = options.cwd, | |
| 1912 | .environ_map = &graph.environ_map, | |
| 1913 | .expand_arg0 = options.expand_arg0, | |
| 1914 | }) catch |err| return .{ .spawn_failed = err }; | |
| 1883 | 1915 | |
| 1884 | 1916 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); |
| 1885 | const stdout = stdout_reader.interface.allocRemaining(b.allocator, .limited(max_output_size)) catch { | |
| 1886 | return error.ReadFailure; | |
| 1917 | const stdout = stdout_reader.interface.allocRemaining(arena, options.stdout_limit) catch |err| switch (err) { | |
| 1918 | error.ReadFailed => panic("failed to read from child: {t}", .{stdout_reader.err.?}), | |
| 1919 | else => |e| panic("failed to read from child: {t}", .{e}), | |
| 1887 | 1920 | }; |
| 1888 | errdefer b.allocator.free(stdout); | |
| 1889 | ||
| 1890 | const term = try child.wait(io); | |
| 1891 | switch (term) { | |
| 1892 | .exited => |code| { | |
| 1893 | if (code != 0) { | |
| 1894 | out_code.* = @as(u8, @truncate(code)); | |
| 1895 | return error.ExitCodeFailure; | |
| 1896 | } | |
| 1897 | return stdout; | |
| 1898 | }, | |
| 1899 | .signal, .stopped => |sig| { | |
| 1900 | out_code.* = @as(u8, @truncate(@intFromEnum(sig))); | |
| 1901 | return error.ProcessTerminated; | |
| 1902 | }, | |
| 1903 | .unknown => |code| { | |
| 1904 | out_code.* = @as(u8, @truncate(code)); | |
| 1905 | return error.ProcessTerminated; | |
| 1921 | ||
| 1922 | const term = child.wait(io) catch @panic("unexpected"); | |
| 1923 | ||
| 1924 | return switch (term) { | |
| 1925 | .exited => |code| switch (code) { | |
| 1926 | 0 => .{ .success = stdout }, | |
| 1927 | else => .{ .bad_exit_code = code }, | |
| 1906 | 1928 | }, |
| 1907 | } | |
| 1929 | .signal, .stopped, .unknown => .crashed, | |
| 1930 | }; | |
| 1908 | 1931 | } |
| 1909 | 1932 | |
| 1910 | /// This is a helper function to be called from build.zig scripts, *not* from | |
| 1911 | /// inside step make() functions. If any errors occur, it fails the build with | |
| 1912 | /// a helpful message. | |
| 1933 | /// Executes the provided command immediately. | |
| 1934 | /// | |
| 1935 | /// If the program exits successfully, stdout is returned. Otherwise, fails the | |
| 1936 | /// build with a helpful message. | |
| 1937 | /// | |
| 1938 | /// See also: | |
| 1939 | /// * `runFallible`. | |
| 1913 | 1940 | pub fn run(b: *Build, argv: []const []const u8) []u8 { |
| 1914 | var code: u8 = undefined; | |
| 1915 | return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal( | |
| 1916 | "the following command failed with {t}:\n{s}", | |
| 1917 | .{ err, Step.allocPrintCmd(b.allocator, .inherit, null, argv) catch @panic("OOM") }, | |
| 1918 | ); | |
| 1941 | const graph = b.graph; | |
| 1942 | const arena = graph.arena; | |
| 1943 | switch (b.runFallible(argv, .{ | |
| 1944 | .stderr_behavior = .inherit, | |
| 1945 | })) { | |
| 1946 | .success => |stdout| return stdout, | |
| 1947 | .spawn_failed => |err| process.fatal("the following command failed with {t}:\n{s}", .{ | |
| 1948 | err, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), | |
| 1949 | }), | |
| 1950 | .bad_exit_code => |code| process.fatal("the following command exited with code {d}:\n{s}", .{ | |
| 1951 | code, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), | |
| 1952 | }), | |
| 1953 | .crashed => process.fatal("the following command crashed:\n{s}", .{ | |
| 1954 | std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), | |
| 1955 | }), | |
| 1956 | } | |
| 1919 | 1957 | } |
| 1920 | 1958 | |
| 1959 | /// Adds additional paths, equivalent to the `--search-prefix` arguments | |
| 1960 | /// provided by the user. Paths added with this function have lower precedence | |
| 1961 | /// than the ones specified by the user on the command line. | |
| 1962 | /// | |
| 1963 | /// It is generally best practice to avoid calling this function, instead | |
| 1964 | /// relying on the user to provide these paths via the standard build system | |
| 1965 | /// interface. However, when integrating with other build systems, the user may | |
| 1966 | /// have already provided the information to the other build system, and thus | |
| 1967 | /// it is desirable to use that same information without requiring the user to | |
| 1968 | /// provide it again. | |
| 1921 | 1969 | pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void { |
| 1922 | b.search_prefixes.append(b.allocator, b.dupePath(search_prefix)) catch @panic("OOM"); | |
| 1970 | if (b.isRoot()) { | |
| 1971 | const graph = b.graph; | |
| 1972 | const wc = &graph.wip_configuration; | |
| 1973 | const string = wc.addString(search_prefix) catch @panic("OOM"); | |
| 1974 | wc.search_prefixes.append(wc.gpa, string) catch @panic("OOM"); | |
| 1975 | } | |
| 1923 | 1976 | } |
| 1924 | 1977 | |
| 1925 | pub fn getInstallPath(b: *Build, dir: InstallDir, dest_rel_path: []const u8) []const u8 { | |
| 1926 | assert(!fs.path.isAbsolute(dest_rel_path)); // Install paths must be relative to the prefix | |
| 1927 | const base_dir = switch (dir) { | |
| 1928 | .prefix => b.install_path, | |
| 1929 | .bin => b.exe_dir, | |
| 1930 | .lib => b.lib_dir, | |
| 1931 | .header => b.h_dir, | |
| 1932 | .custom => |p| b.pathJoin(&.{ b.install_path, p }), | |
| 1933 | }; | |
| 1934 | return b.pathResolve(&.{ base_dir, dest_rel_path }); | |
| 1978 | pub fn isRoot(b: *const Build) bool { | |
| 1979 | return b.pkg_hash.len == 0; | |
| 1935 | 1980 | } |
| 1936 | 1981 | |
| 1937 | 1982 | pub const Dependency = struct { |
| ... | ... | @@ -1987,14 +2032,15 @@ fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 { |
| 1987 | 2032 | for (b.available_deps) |dep| { |
| 1988 | 2033 | if (mem.eql(u8, dep[0], name)) return dep[1]; |
| 1989 | 2034 | } |
| 1990 | ||
| 1991 | const full_path = b.pathFromRoot("build.zig.zon"); | |
| 1992 | std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ name, full_path }); | |
| 2035 | std.log.info("all dependencies used by build.zig must be declared in corresponding build.zig.zon", .{}); | |
| 2036 | if (b.pkg_hash.len == 0) panic("no dependency named {s}", .{name}); | |
| 2037 | panic("no dependency named {s} in {s} ({s})", .{ name, b.dep_prefix, b.pkg_hash }); | |
| 1993 | 2038 | } |
| 1994 | 2039 | |
| 1995 | 2040 | inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, comptime dep_name: []const u8) []const u8 { |
| 1996 | 2041 | const build_runner = @import("root"); |
| 1997 | 2042 | const deps = build_runner.dependencies; |
| 2043 | const arena = b.graph.arena; | |
| 1998 | 2044 | |
| 1999 | 2045 | const b_pkg_hash, const b_pkg_deps = comptime for (@typeInfo(deps.packages).@"struct".decls) |decl| { |
| 2000 | 2046 | const pkg_hash = decl.name; |
| ... | ... | @@ -2002,14 +2048,19 @@ inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, c |
| 2002 | 2048 | if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps }; |
| 2003 | 2049 | } else .{ "", deps.root_deps }; |
| 2004 | 2050 | if (!std.mem.eql(u8, b_pkg_hash, b.pkg_hash)) { |
| 2005 | std.debug.panic("'{}' is not the struct that corresponds to '{s}'", .{ asking_build_zig, b.pathFromRoot("build.zig") }); | |
| 2051 | const build_zig_path = b.root.join(arena, "build.zig") catch @panic("OOM"); | |
| 2052 | panic("{} is not the struct that corresponds to {f}", .{ | |
| 2053 | asking_build_zig, build_zig_path, | |
| 2054 | }); | |
| 2006 | 2055 | } |
| 2007 | 2056 | comptime for (b_pkg_deps) |dep| { |
| 2008 | 2057 | if (std.mem.eql(u8, dep[0], dep_name)) return dep[1]; |
| 2009 | 2058 | }; |
| 2010 | 2059 | |
| 2011 | const full_path = b.pathFromRoot("build.zig.zon"); | |
| 2012 | std.debug.panic("no dependency named '{s}' in '{s}'. All packages used in build.zig must be declared in this file", .{ dep_name, full_path }); | |
| 2060 | const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM"); | |
| 2061 | panic("no dependency named {s} in {f}. All packages used in build.zig must be declared in this file", .{ | |
| 2062 | dep_name, full_path, | |
| 2063 | }); | |
| 2013 | 2064 | } |
| 2014 | 2065 | |
| 2015 | 2066 | fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void { |
| ... | ... | @@ -2026,6 +2077,7 @@ fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void { |
| 2026 | 2077 | /// In other words, if this function returns `null` it means that the only |
| 2027 | 2078 | /// purpose of completing the configure phase is to find out all the other lazy |
| 2028 | 2079 | /// dependencies that are also required. |
| 2080 | /// | |
| 2029 | 2081 | /// It is allowed to use this function for non-lazy dependencies, in which case |
| 2030 | 2082 | /// it will never return `null`. This allows toggling laziness via |
| 2031 | 2083 | /// build.zig.zon without changing build.zig logic. |
| ... | ... | @@ -2058,7 +2110,7 @@ pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency { |
| 2058 | 2110 | if (mem.eql(u8, decl.name, pkg_hash)) { |
| 2059 | 2111 | const pkg = @field(deps.packages, decl.name); |
| 2060 | 2112 | if (@hasDecl(pkg, "available")) { |
| 2061 | std.debug.panic("dependency '{s}{s}' is marked as lazy in build.zig.zon which means it must use the lazyDependency function instead", .{ b.dep_prefix, name }); | |
| 2113 | panic("dependency '{s}{s}' is marked as lazy in build.zig.zon which means it must use the lazyDependency function instead", .{ b.dep_prefix, name }); | |
| 2062 | 2114 | } |
| 2063 | 2115 | return dependencyInner(b, name, pkg.build_root, if (@hasDecl(pkg, "build_zig")) pkg.build_zig else null, pkg_hash, pkg.deps, args); |
| 2064 | 2116 | } |
| ... | ... | @@ -2111,6 +2163,8 @@ pub fn dependencyFromBuildZig( |
| 2111 | 2163 | ) *Dependency { |
| 2112 | 2164 | const build_runner = @import("root"); |
| 2113 | 2165 | const deps = build_runner.dependencies; |
| 2166 | const graph = b.graph; | |
| 2167 | const arena = graph.arena; | |
| 2114 | 2168 | |
| 2115 | 2169 | find_dep: { |
| 2116 | 2170 | const pkg, const pkg_hash = inline for (@typeInfo(deps.packages).@"struct".decls) |decl| { |
| ... | ... | @@ -2124,8 +2178,8 @@ pub fn dependencyFromBuildZig( |
| 2124 | 2178 | return dependencyInner(b, dep_name, pkg.build_root, pkg.build_zig, pkg_hash, pkg.deps, args); |
| 2125 | 2179 | } |
| 2126 | 2180 | |
| 2127 | const full_path = b.pathFromRoot("build.zig.zon"); | |
| 2128 | std.debug.panic("'{}' is not a build.zig struct of a dependency in '{s}'", .{ build_zig, full_path }); | |
| 2181 | const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM"); | |
| 2182 | panic("{} is not a build.zig struct of a dependency in {f}", .{ build_zig, full_path }); | |
| 2129 | 2183 | } |
| 2130 | 2184 | |
| 2131 | 2185 | fn userValuesAreSame(lhs: UserValue, rhs: UserValue) bool { |
| ... | ... | @@ -2188,10 +2242,10 @@ fn userLazyPathsAreTheSame(lhs_lp: LazyPath, rhs_lp: LazyPath) bool { |
| 2188 | 2242 | if (lhs_sp.owner != rhs_sp.owner) return false; |
| 2189 | 2243 | if (std.mem.eql(u8, lhs_sp.sub_path, rhs_sp.sub_path)) return false; |
| 2190 | 2244 | }, |
| 2191 | .generated => |lhs_gen| { | |
| 2192 | const rhs_gen = rhs_lp.generated; | |
| 2245 | .generated => |*lhs_gen| { | |
| 2246 | const rhs_gen = &rhs_lp.generated; | |
| 2193 | 2247 | |
| 2194 | if (lhs_gen.file != rhs_gen.file) return false; | |
| 2248 | if (lhs_gen.index != rhs_gen.index) return false; | |
| 2195 | 2249 | if (lhs_gen.up != rhs_gen.up) return false; |
| 2196 | 2250 | if (std.mem.eql(u8, lhs_gen.sub_path, rhs_gen.sub_path)) return false; |
| 2197 | 2251 | }, |
| ... | ... | @@ -2200,6 +2254,7 @@ fn userLazyPathsAreTheSame(lhs_lp: LazyPath, rhs_lp: LazyPath) bool { |
| 2200 | 2254 | |
| 2201 | 2255 | if (!std.mem.eql(u8, lhs_rel_path, rhs_rel_path)) return false; |
| 2202 | 2256 | }, |
| 2257 | .relative => |lhs| return lhs.eql(rhs_lp.relative), | |
| 2203 | 2258 | .dependency => |lhs_dep| { |
| 2204 | 2259 | const rhs_dep = rhs_lp.dependency; |
| 2205 | 2260 | |
| ... | ... | @@ -2219,25 +2274,25 @@ fn dependencyInner( |
| 2219 | 2274 | pkg_deps: AvailableDeps, |
| 2220 | 2275 | args: anytype, |
| 2221 | 2276 | ) *Dependency { |
| 2222 | const io = b.graph.io; | |
| 2223 | const user_input_options = userInputOptionsFromArgs(b.allocator, args); | |
| 2224 | if (b.graph.dependency_cache.getContext(.{ | |
| 2277 | const graph = b.graph; | |
| 2278 | const io = graph.io; | |
| 2279 | const arena = graph.arena; | |
| 2280 | const user_input_options = userInputOptionsFromArgs(arena, args); | |
| 2281 | if (graph.dependency_cache.getContext(.{ | |
| 2225 | 2282 | .build_root_string = build_root_string, |
| 2226 | 2283 | .user_input_options = user_input_options, |
| 2227 | }, .{ .allocator = b.graph.arena })) |dep| | |
| 2228 | return dep; | |
| 2229 | ||
| 2230 | const build_root: std.Build.Cache.Directory = .{ | |
| 2231 | .path = build_root_string, | |
| 2232 | .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err| { | |
| 2233 | std.debug.print("unable to open '{s}': {s}\n", .{ | |
| 2234 | build_root_string, @errorName(err), | |
| 2235 | }); | |
| 2236 | process.exit(1); | |
| 2284 | }, .{ .allocator = arena })) |dep| return dep; | |
| 2285 | ||
| 2286 | const dep_root: Cache.Path = .{ | |
| 2287 | .root_dir = .{ | |
| 2288 | .path = build_root_string, | |
| 2289 | .handle = Io.Dir.cwd().openDir(io, build_root_string, .{}) catch |err| | |
| 2290 | process.fatal("unable to open {s}: {t}", .{ build_root_string, err }), | |
| 2237 | 2291 | }, |
| 2238 | 2292 | }; |
| 2239 | 2293 | |
| 2240 | const sub_builder = b.createChild(name, build_root, pkg_hash, pkg_deps, user_input_options) catch @panic("unhandled error"); | |
| 2294 | const sub_builder = b.createChild(name, dep_root, pkg_hash, pkg_deps, user_input_options) catch | |
| 2295 | @panic("unhandled error"); | |
| 2241 | 2296 | if (build_zig) |bz| { |
| 2242 | 2297 | sub_builder.runBuild(bz) catch @panic("unhandled error"); |
| 2243 | 2298 | |
| ... | ... | @@ -2246,13 +2301,13 @@ fn dependencyInner( |
| 2246 | 2301 | } |
| 2247 | 2302 | } |
| 2248 | 2303 | |
| 2249 | const dep = b.allocator.create(Dependency) catch @panic("OOM"); | |
| 2304 | const dep = graph.create(Dependency); | |
| 2250 | 2305 | dep.* = .{ .builder = sub_builder }; |
| 2251 | 2306 | |
| 2252 | b.graph.dependency_cache.putContext(b.graph.arena, .{ | |
| 2307 | graph.dependency_cache.putContext(arena, .{ | |
| 2253 | 2308 | .build_root_string = build_root_string, |
| 2254 | 2309 | .user_input_options = user_input_options, |
| 2255 | }, dep, .{ .allocator = b.graph.arena }) catch @panic("OOM"); | |
| 2310 | }, dep, .{ .allocator = arena }) catch @panic("OOM"); | |
| 2256 | 2311 | return dep; |
| 2257 | 2312 | } |
| 2258 | 2313 | |
| ... | ... | @@ -2264,41 +2319,6 @@ pub fn runBuild(b: *Build, build_zig: anytype) anyerror!void { |
| 2264 | 2319 | } |
| 2265 | 2320 | } |
| 2266 | 2321 | |
| 2267 | /// A file that is generated by a build step. | |
| 2268 | /// This struct is an interface that is meant to be used with `@fieldParentPtr` to implement the actual path logic. | |
| 2269 | pub const GeneratedFile = struct { | |
| 2270 | /// The step that generates the file. | |
| 2271 | step: *Step, | |
| 2272 | /// The path to the generated file. Must be either absolute or relative to the build runner cwd. | |
| 2273 | /// This value must be set in the `fn make()` of the `step` and must not be `null` afterwards. | |
| 2274 | path: ?[]const u8 = null, | |
| 2275 | ||
| 2276 | /// Deprecated, see `getPath3`. | |
| 2277 | pub fn getPath(gen: GeneratedFile) []const u8 { | |
| 2278 | return gen.step.owner.pathFromCwd(gen.path orelse std.debug.panic( | |
| 2279 | "getPath() was called on a GeneratedFile that wasn't built yet. Is there a missing Step dependency on step '{s}'?", | |
| 2280 | .{gen.step.name}, | |
| 2281 | )); | |
| 2282 | } | |
| 2283 | ||
| 2284 | /// Deprecated, see `getPath3`. | |
| 2285 | pub fn getPath2(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) []const u8 { | |
| 2286 | return getPath3(gen, src_builder, asking_step) catch |err| switch (err) { | |
| 2287 | error.Canceled => std.process.exit(1), | |
| 2288 | }; | |
| 2289 | } | |
| 2290 | ||
| 2291 | pub fn getPath3(gen: GeneratedFile, src_builder: *Build, asking_step: ?*Step) Io.Cancelable![]const u8 { | |
| 2292 | return gen.path orelse { | |
| 2293 | const graph = gen.step.owner.graph; | |
| 2294 | const io = graph.io; | |
| 2295 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 2296 | dumpBadGetPathHelp(gen.step, stderr.terminal(), src_builder, asking_step) catch {}; | |
| 2297 | @panic("misconfigured build script"); | |
| 2298 | }; | |
| 2299 | } | |
| 2300 | }; | |
| 2301 | ||
| 2302 | 2322 | // dirnameAllowEmpty is a variant of fs.path.dirname |
| 2303 | 2323 | // that allows "" to refer to the root for relative paths. |
| 2304 | 2324 | // |
| ... | ... | @@ -2338,7 +2358,7 @@ pub const LazyPath = union(enum) { |
| 2338 | 2358 | }, |
| 2339 | 2359 | |
| 2340 | 2360 | generated: struct { |
| 2341 | file: *const GeneratedFile, | |
| 2361 | index: Configuration.GeneratedFileIndex, | |
| 2342 | 2362 | |
| 2343 | 2363 | /// The number of parent directories to go up. |
| 2344 | 2364 | /// 0 means the generated file itself. |
| ... | ... | @@ -2350,14 +2370,7 @@ pub const LazyPath = union(enum) { |
| 2350 | 2370 | sub_path: []const u8 = "", |
| 2351 | 2371 | }, |
| 2352 | 2372 | |
| 2353 | /// An absolute path or a path relative to the current working directory of | |
| 2354 | /// the build runner process. | |
| 2355 | /// | |
| 2356 | /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which | |
| 2357 | /// ignore the file system path of build.zig and instead are relative to the directory from | |
| 2358 | /// which `zig build` was invoked. | |
| 2359 | /// | |
| 2360 | /// Use of this tag indicates a dependency on the host system. | |
| 2373 | /// Deprecated; call `Graph.cwdRelativePath` instead. | |
| 2361 | 2374 | cwd_relative: []const u8, |
| 2362 | 2375 | |
| 2363 | 2376 | dependency: struct { |
| ... | ... | @@ -2365,13 +2378,30 @@ pub const LazyPath = union(enum) { |
| 2365 | 2378 | sub_path: []const u8, |
| 2366 | 2379 | }, |
| 2367 | 2380 | |
| 2381 | relative: struct { | |
| 2382 | base: Configuration.Path.Base, | |
| 2383 | sub_path: []const u8 = "", | |
| 2384 | ||
| 2385 | pub fn eql(a: @This(), b: @This()) bool { | |
| 2386 | return a.base == b.base and mem.eql(u8, a.sub_path, b.sub_path); | |
| 2387 | } | |
| 2388 | }, | |
| 2389 | ||
| 2390 | /// Path to the Zig executable being used to execute "zig build". | |
| 2391 | pub const zig_exe: LazyPath = .{ .relative = .{ .base = .zig_exe } }; | |
| 2392 | /// Path to the "lib/" directory from the Zig installation being used to | |
| 2393 | /// execute "zig build". | |
| 2394 | pub const zig_lib: LazyPath = .{ .relative = .{ .base = .zig_lib } }; | |
| 2395 | /// Path to the project's local cache directory (usually called ".zig-cache"). | |
| 2396 | pub const cache_root: LazyPath = .{ .relative = .{ .base = .local_cache } }; | |
| 2397 | ||
| 2368 | 2398 | /// Returns a lazy path referring to the directory containing this path. |
| 2369 | 2399 | /// |
| 2370 | /// The dirname is not allowed to escape the logical root for underlying path. | |
| 2371 | /// For example, if the path is relative to the build root, | |
| 2372 | /// the dirname is not allowed to traverse outside of the build root. | |
| 2373 | /// Similarly, if the path is a generated file inside zig-cache, | |
| 2374 | /// the dirname is not allowed to traverse outside of zig-cache. | |
| 2400 | /// The dirname is not allowed to escape the logical root for underlying | |
| 2401 | /// path. For example, if the path is relative to the build root, the | |
| 2402 | /// dirname is not allowed to traverse outside of the build root. | |
| 2403 | /// Similarly, if the path is a generated file inside zig-cache, the | |
| 2404 | /// dirname is not allowed to traverse outside of zig-cache. | |
| 2375 | 2405 | pub fn dirname(lazy_path: LazyPath) LazyPath { |
| 2376 | 2406 | return switch (lazy_path) { |
| 2377 | 2407 | .src_path => |sp| .{ .src_path = .{ |
| ... | ... | @@ -2382,11 +2412,11 @@ pub const LazyPath = union(enum) { |
| 2382 | 2412 | }, |
| 2383 | 2413 | } }, |
| 2384 | 2414 | .generated => |generated| .{ .generated = if (dirnameAllowEmpty(generated.sub_path)) |sub_dirname| .{ |
| 2385 | .file = generated.file, | |
| 2415 | .index = generated.index, | |
| 2386 | 2416 | .up = generated.up, |
| 2387 | 2417 | .sub_path = sub_dirname, |
| 2388 | 2418 | } else .{ |
| 2389 | .file = generated.file, | |
| 2419 | .index = generated.index, | |
| 2390 | 2420 | .up = generated.up + 1, |
| 2391 | 2421 | .sub_path = "", |
| 2392 | 2422 | } }, |
| ... | ... | @@ -2413,6 +2443,13 @@ pub const LazyPath = union(enum) { |
| 2413 | 2443 | } |
| 2414 | 2444 | }, |
| 2415 | 2445 | }, |
| 2446 | .relative => |r| .{ .relative = .{ | |
| 2447 | .base = r.base, | |
| 2448 | .sub_path = dirnameAllowEmpty(r.sub_path) orelse { | |
| 2449 | dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the base path\n", .{}) catch {}; | |
| 2450 | @panic("misconfigured build script"); | |
| 2451 | }, | |
| 2452 | } }, | |
| 2416 | 2453 | .dependency => |dep| .{ .dependency = .{ |
| 2417 | 2454 | .dependency = dep.dependency, |
| 2418 | 2455 | .sub_path = dirnameAllowEmpty(dep.sub_path) orelse { |
| ... | ... | @@ -2427,7 +2464,9 @@ pub const LazyPath = union(enum) { |
| 2427 | 2464 | } |
| 2428 | 2465 | |
| 2429 | 2466 | pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath { |
| 2430 | return lazy_path.join(b.allocator, sub_path) catch @panic("OOM"); | |
| 2467 | const graph = b.graph; | |
| 2468 | const arena = graph.arena; | |
| 2469 | return lazy_path.join(arena, sub_path) catch @panic("OOM"); | |
| 2431 | 2470 | } |
| 2432 | 2471 | |
| 2433 | 2472 | pub fn join(lazy_path: LazyPath, arena: Allocator, sub_path: []const u8) Allocator.Error!LazyPath { |
| ... | ... | @@ -2437,13 +2476,17 @@ pub const LazyPath = union(enum) { |
| 2437 | 2476 | .sub_path = try fs.path.resolve(arena, &.{ src.sub_path, sub_path }), |
| 2438 | 2477 | } }, |
| 2439 | 2478 | .generated => |gen| .{ .generated = .{ |
| 2440 | .file = gen.file, | |
| 2479 | .index = gen.index, | |
| 2441 | 2480 | .up = gen.up, |
| 2442 | 2481 | .sub_path = try fs.path.resolve(arena, &.{ gen.sub_path, sub_path }), |
| 2443 | 2482 | } }, |
| 2444 | 2483 | .cwd_relative => |cwd_relative| .{ |
| 2445 | 2484 | .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }), |
| 2446 | 2485 | }, |
| 2486 | .relative => |r| .{ .relative = .{ | |
| 2487 | .base = r.base, | |
| 2488 | .sub_path = try fs.path.resolve(arena, &.{ r.sub_path, sub_path }), | |
| 2489 | } }, | |
| 2447 | 2490 | .dependency => |dep| .{ .dependency = .{ |
| 2448 | 2491 | .dependency = dep.dependency, |
| 2449 | 2492 | .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }), |
| ... | ... | @@ -2451,148 +2494,59 @@ pub const LazyPath = union(enum) { |
| 2451 | 2494 | }; |
| 2452 | 2495 | } |
| 2453 | 2496 | |
| 2454 | /// Returns a string that can be shown to represent the file source. | |
| 2455 | /// Either returns the path, `"generated"`, or `"dependency"`. | |
| 2497 | /// Deprecated, use `format` instead. | |
| 2456 | 2498 | pub fn getDisplayName(lazy_path: LazyPath) []const u8 { |
| 2457 | 2499 | return switch (lazy_path) { |
| 2458 | 2500 | .src_path => |sp| sp.sub_path, |
| 2459 | 2501 | .cwd_relative => |p| p, |
| 2460 | 2502 | .generated => "generated", |
| 2461 | 2503 | .dependency => "dependency", |
| 2504 | .relative => |r| @tagName(r.base), | |
| 2462 | 2505 | }; |
| 2463 | 2506 | } |
| 2464 | 2507 | |
| 2465 | /// Adds dependencies this file source implies to the given step. | |
| 2466 | pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void { | |
| 2467 | switch (lazy_path) { | |
| 2468 | .src_path, .cwd_relative, .dependency => {}, | |
| 2469 | .generated => |gen| other_step.dependOn(gen.file.step), | |
| 2508 | pub fn format(lp: LazyPath, w: *Io.Writer) Io.Writer.Error!void { | |
| 2509 | switch (lp) { | |
| 2510 | .src_path => |sp| try w.writeAll(sp.sub_path), | |
| 2511 | .cwd_relative => |p| try w.writeAll(p), | |
| 2512 | .generated => try w.writeAll("generated"), | |
| 2513 | .dependency => try w.writeAll("dependency"), | |
| 2514 | .relative => |r| try w.print("{t} {s}", .{ r.base, r.sub_path }), | |
| 2470 | 2515 | } |
| 2471 | 2516 | } |
| 2472 | 2517 | |
| 2473 | /// Deprecated, see `getPath4`. | |
| 2474 | pub fn getPath(lazy_path: LazyPath, src_builder: *Build) []const u8 { | |
| 2475 | return getPath2(lazy_path, src_builder, null); | |
| 2476 | } | |
| 2477 | ||
| 2478 | /// Deprecated, see `getPath4`. | |
| 2479 | pub fn getPath2(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 { | |
| 2480 | const p = getPath3(lazy_path, src_builder, asking_step); | |
| 2481 | return src_builder.pathResolve(&.{ p.root_dir.path orelse ".", p.sub_path }); | |
| 2482 | } | |
| 2483 | ||
| 2484 | /// Deprecated, see `getPath4`. | |
| 2485 | pub fn getPath3(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Cache.Path { | |
| 2486 | return getPath4(lazy_path, src_builder, asking_step) catch |err| switch (err) { | |
| 2487 | error.Canceled => std.process.exit(1), | |
| 2488 | }; | |
| 2489 | } | |
| 2490 | ||
| 2491 | /// Intended to be used during the make phase only. | |
| 2492 | /// | |
| 2493 | /// `asking_step` is only used for debugging purposes; it's the step being | |
| 2494 | /// run that is asking for the path. | |
| 2495 | pub fn getPath4(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) Io.Cancelable!Cache.Path { | |
| 2518 | /// Adds dependencies this file source implies to the given step. | |
| 2519 | pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void { | |
| 2496 | 2520 | switch (lazy_path) { |
| 2497 | .src_path => |sp| return .{ | |
| 2498 | .root_dir = sp.owner.build_root, | |
| 2499 | .sub_path = sp.sub_path, | |
| 2500 | }, | |
| 2501 | .cwd_relative => |sub_path| return .{ | |
| 2502 | .root_dir = Cache.Directory.cwd(), | |
| 2503 | .sub_path = sub_path, | |
| 2504 | }, | |
| 2521 | .src_path, .cwd_relative, .relative, .dependency => {}, | |
| 2505 | 2522 | .generated => |gen| { |
| 2506 | // TODO make gen.file.path not be absolute and use that as the | |
| 2507 | // basis for not traversing up too many directories. | |
| 2508 | ||
| 2509 | const graph = src_builder.graph; | |
| 2510 | ||
| 2511 | var file_path: Cache.Path = .{ | |
| 2512 | .root_dir = Cache.Directory.cwd(), | |
| 2513 | .sub_path = gen.file.path orelse { | |
| 2514 | const io = graph.io; | |
| 2515 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 2516 | dumpBadGetPathHelp(gen.file.step, stderr.terminal(), src_builder, asking_step) catch {}; | |
| 2517 | io.unlockStderr(); | |
| 2518 | @panic("misconfigured build script"); | |
| 2519 | }, | |
| 2520 | }; | |
| 2521 | ||
| 2522 | if (gen.up > 0) { | |
| 2523 | const cache_root_path = src_builder.cache_root.path orelse | |
| 2524 | (src_builder.cache_root.join(src_builder.allocator, &.{"."}) catch @panic("OOM")); | |
| 2525 | ||
| 2526 | for (0..gen.up) |_| { | |
| 2527 | if (mem.eql(u8, file_path.sub_path, cache_root_path)) { | |
| 2528 | // If we hit the cache root and there's still more to go, | |
| 2529 | // the script attempted to go too far. | |
| 2530 | dumpBadDirnameHelp(gen.file.step, asking_step, | |
| 2531 | \\dirname() attempted to traverse outside the cache root. | |
| 2532 | \\This is not allowed. | |
| 2533 | \\ | |
| 2534 | , .{}) catch {}; | |
| 2535 | @panic("misconfigured build script"); | |
| 2536 | } | |
| 2537 | ||
| 2538 | // path is absolute. | |
| 2539 | // dirname will return null only if we're at root. | |
| 2540 | // Typically, we'll stop well before that at the cache root. | |
| 2541 | file_path.sub_path = fs.path.dirname(file_path.sub_path) orelse { | |
| 2542 | dumpBadDirnameHelp(gen.file.step, asking_step, | |
| 2543 | \\dirname() reached root. | |
| 2544 | \\No more directories left to go up. | |
| 2545 | \\ | |
| 2546 | , .{}) catch {}; | |
| 2547 | @panic("misconfigured build script"); | |
| 2548 | }; | |
| 2549 | } | |
| 2550 | } | |
| 2551 | ||
| 2552 | return file_path.join(src_builder.allocator, gen.sub_path) catch @panic("OOM"); | |
| 2553 | }, | |
| 2554 | .dependency => |dep| return .{ | |
| 2555 | .root_dir = dep.dependency.builder.build_root, | |
| 2556 | .sub_path = dep.sub_path, | |
| 2523 | const graph = other_step.owner.graph; | |
| 2524 | const generated_owner_step = graph.generated_files.items[@intFromEnum(gen.index)]; | |
| 2525 | other_step.dependOn(generated_owner_step); | |
| 2557 | 2526 | }, |
| 2558 | 2527 | } |
| 2559 | 2528 | } |
| 2560 | 2529 | |
| 2561 | pub fn basename(lazy_path: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 { | |
| 2562 | return fs.path.basename(switch (lazy_path) { | |
| 2563 | .src_path => |sp| sp.sub_path, | |
| 2564 | .cwd_relative => |sub_path| sub_path, | |
| 2565 | .generated => |gen| if (gen.sub_path.len > 0) | |
| 2566 | gen.sub_path | |
| 2567 | else | |
| 2568 | gen.file.getPath2(src_builder, asking_step), | |
| 2569 | .dependency => |dep| dep.sub_path, | |
| 2570 | }); | |
| 2571 | } | |
| 2572 | ||
| 2573 | 2530 | /// Copies the internal strings. |
| 2574 | 2531 | /// |
| 2575 | /// The `b` parameter is only used for its allocator. All *Build instances | |
| 2576 | /// share the same allocator. | |
| 2577 | pub fn dupe(lazy_path: LazyPath, b: *Build) LazyPath { | |
| 2578 | return lazy_path.dupeInner(b.allocator); | |
| 2532 | /// The `graph` parameter is only used for the global arena allocator. | |
| 2533 | pub fn dupe(lazy_path: LazyPath, graph: *const Graph) LazyPath { | |
| 2534 | return dupeInner(lazy_path, graph.arena); | |
| 2579 | 2535 | } |
| 2580 | 2536 | |
| 2581 | fn dupeInner(lazy_path: LazyPath, allocator: std.mem.Allocator) LazyPath { | |
| 2537 | fn dupeInner(lazy_path: LazyPath, arena: Allocator) LazyPath { | |
| 2582 | 2538 | return switch (lazy_path) { |
| 2583 | .src_path => |sp| .{ .src_path = .{ | |
| 2584 | .owner = sp.owner, | |
| 2585 | .sub_path = sp.owner.dupePath(sp.sub_path), | |
| 2586 | } }, | |
| 2587 | .cwd_relative => |p| .{ .cwd_relative = dupePathInner(allocator, p) }, | |
| 2539 | .src_path => |sp| .{ .src_path = .{ .owner = sp.owner, .sub_path = sp.owner.dupePath(sp.sub_path) } }, | |
| 2540 | .cwd_relative => |p| .{ .cwd_relative = Graph.dupePathInner(arena, p) }, | |
| 2541 | .relative => |r| .{ .relative = r }, | |
| 2588 | 2542 | .generated => |gen| .{ .generated = .{ |
| 2589 | .file = gen.file, | |
| 2543 | .index = gen.index, | |
| 2590 | 2544 | .up = gen.up, |
| 2591 | .sub_path = dupePathInner(allocator, gen.sub_path), | |
| 2545 | .sub_path = Graph.dupePathInner(arena, gen.sub_path), | |
| 2592 | 2546 | } }, |
| 2593 | 2547 | .dependency => |dep| .{ .dependency = .{ |
| 2594 | 2548 | .dependency = dep.dependency, |
| 2595 | .sub_path = dupePathInner(allocator, dep.sub_path), | |
| 2549 | .sub_path = Graph.dupePathInner(arena, dep.sub_path), | |
| 2596 | 2550 | } }, |
| 2597 | 2551 | }; |
| 2598 | 2552 | } |
| ... | ... | @@ -2631,36 +2585,6 @@ fn dumpBadDirnameHelp( |
| 2631 | 2585 | stderr.setColor(.reset) catch {}; |
| 2632 | 2586 | } |
| 2633 | 2587 | |
| 2634 | /// In this function the stderr mutex has already been locked. | |
| 2635 | pub fn dumpBadGetPathHelp(s: *Step, t: Io.Terminal, src_builder: *Build, asking_step: ?*Step) anyerror!void { | |
| 2636 | const w = t.writer; | |
| 2637 | try w.print( | |
| 2638 | \\getPath() was called on a GeneratedFile that wasn't built yet. | |
| 2639 | \\ source package path: {s} | |
| 2640 | \\ Is there a missing Step dependency on step '{s}'? | |
| 2641 | \\ | |
| 2642 | , .{ | |
| 2643 | src_builder.build_root.path orelse ".", | |
| 2644 | s.name, | |
| 2645 | }); | |
| 2646 | ||
| 2647 | t.setColor(.red) catch {}; | |
| 2648 | try w.writeAll(" The step was created by this stack trace:\n"); | |
| 2649 | t.setColor(.reset) catch {}; | |
| 2650 | ||
| 2651 | s.dump(t); | |
| 2652 | if (asking_step) |as| { | |
| 2653 | t.setColor(.red) catch {}; | |
| 2654 | try w.print(" The step '{s}' that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); | |
| 2655 | t.setColor(.reset) catch {}; | |
| 2656 | ||
| 2657 | as.dump(t); | |
| 2658 | } | |
| 2659 | t.setColor(.red) catch {}; | |
| 2660 | try w.writeAll(" Proceeding to panic.\n"); | |
| 2661 | t.setColor(.reset) catch {}; | |
| 2662 | } | |
| 2663 | ||
| 2664 | 2588 | pub const InstallDir = union(enum) { |
| 2665 | 2589 | prefix: void, |
| 2666 | 2590 | lib: void, |
| ... | ... | @@ -2670,18 +2594,18 @@ pub const InstallDir = union(enum) { |
| 2670 | 2594 | custom: []const u8, |
| 2671 | 2595 | |
| 2672 | 2596 | /// Duplicates the install directory including the path if set to custom. |
| 2673 | pub fn dupe(dir: InstallDir, builder: *Build) InstallDir { | |
| 2597 | pub fn dupe(dir: InstallDir, graph: *const Graph) InstallDir { | |
| 2674 | 2598 | if (dir == .custom) { |
| 2675 | return .{ .custom = builder.dupe(dir.custom) }; | |
| 2599 | return .{ .custom = graph.dupeString(dir.custom) }; | |
| 2676 | 2600 | } else { |
| 2677 | 2601 | return dir; |
| 2678 | 2602 | } |
| 2679 | 2603 | } |
| 2680 | 2604 | }; |
| 2681 | 2605 | |
| 2682 | /// Creates a path leading to a directory inside "tmp" subdirectory of | |
| 2683 | /// `cache_root` which is created on demand and cleaned up by the build runner | |
| 2684 | /// upon success. | |
| 2606 | /// Creates a path leading to a directory inside "tmp" subdirectory of local | |
| 2607 | /// cache which is created on demand and cleaned up by the build runner upon | |
| 2608 | /// success. | |
| 2685 | 2609 | pub fn tmpPath(b: *Build) LazyPath { |
| 2686 | 2610 | const wf = b.addTempFiles(); |
| 2687 | 2611 | return wf.getDirectory(); |
| ... | ... | @@ -2725,7 +2649,9 @@ pub fn systemIntegrationOption( |
| 2725 | 2649 | name: []const u8, |
| 2726 | 2650 | config: SystemIntegrationOptionConfig, |
| 2727 | 2651 | ) bool { |
| 2728 | const gop = b.graph.system_library_options.getOrPut(b.allocator, name) catch @panic("OOM"); | |
| 2652 | const graph = b.graph; | |
| 2653 | const arena = graph.arena; | |
| 2654 | const gop = graph.system_integration_options.getOrPut(arena, name) catch @panic("OOM"); | |
| 2729 | 2655 | if (gop.found_existing) switch (gop.value_ptr.*) { |
| 2730 | 2656 | .user_disabled => { |
| 2731 | 2657 | gop.value_ptr.* = .declared_disabled; |
| ... | ... | @@ -2738,8 +2664,8 @@ pub fn systemIntegrationOption( |
| 2738 | 2664 | .declared_disabled => return false, |
| 2739 | 2665 | .declared_enabled => return true, |
| 2740 | 2666 | } else { |
| 2741 | gop.key_ptr.* = b.dupe(name); | |
| 2742 | if (config.default orelse b.graph.system_package_mode) { | |
| 2667 | gop.key_ptr.* = graph.dupeString(name); | |
| 2668 | if (config.default orelse graph.system_package_mode) { | |
| 2743 | 2669 | gop.value_ptr.* = .declared_enabled; |
| 2744 | 2670 | return true; |
| 2745 | 2671 | } else { |
lib/std/Build/Cache.zig+10-1| ... | ... | @@ -189,12 +189,15 @@ pub const File = struct { |
| 189 | 189 | pub const HashHelper = struct { |
| 190 | 190 | hasher: Hasher = hasher_init, |
| 191 | 191 | |
| 192 | /// Record a slice of bytes as a dependency of the process being cached. | |
| 193 | 192 | pub fn addBytes(hh: *HashHelper, bytes: []const u8) void { |
| 194 | 193 | hh.hasher.update(mem.asBytes(&bytes.len)); |
| 195 | 194 | hh.hasher.update(bytes); |
| 196 | 195 | } |
| 197 | 196 | |
| 197 | pub fn addBytesZ(hh: *HashHelper, bytes: [:0]const u8) void { | |
| 198 | hh.hasher.update(mem.absorbSentinel(bytes)); | |
| 199 | } | |
| 200 | ||
| 198 | 201 | pub fn addOptionalBytes(hh: *HashHelper, optional_bytes: ?[]const u8) void { |
| 199 | 202 | hh.add(optional_bytes != null); |
| 200 | 203 | hh.addBytes(optional_bytes orelse return); |
| ... | ... | @@ -1024,6 +1027,12 @@ pub const Manifest = struct { |
| 1024 | 1027 | try self.populateFileHash(gop.key_ptr); |
| 1025 | 1028 | } |
| 1026 | 1029 | |
| 1030 | pub fn addPathPost(man: *Manifest, path: Path) !void { | |
| 1031 | _ = man; | |
| 1032 | _ = path; | |
| 1033 | @panic("TODO"); | |
| 1034 | } | |
| 1035 | ||
| 1027 | 1036 | /// Like `addFilePost` but when the file contents have already been loaded from disk. |
| 1028 | 1037 | pub fn addFilePostContents( |
| 1029 | 1038 | self: *Manifest, |
lib/std/Build/Cache/Path.zig+8-1| ... | ... | @@ -24,7 +24,7 @@ pub fn cwd() Path { |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | 26 | pub fn initCwd(sub_path: []const u8) Path { |
| 27 | return .{ .root_dir = Cache.Directory.cwd(), .sub_path = sub_path }; | |
| 27 | return .{ .root_dir = .cwd(), .sub_path = sub_path }; | |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | pub fn join(p: Path, arena: Allocator, sub_path: []const u8) Allocator.Error!Path { |
| ... | ... | @@ -213,6 +213,13 @@ pub fn stem(p: Path) []const u8 { |
| 213 | 213 | return fs.path.stem(p.sub_path); |
| 214 | 214 | } |
| 215 | 215 | |
| 216 | pub fn dirname(p: Path) ?Path { | |
| 217 | return .{ | |
| 218 | .root_dir = p.root_dir, | |
| 219 | .sub_path = fs.path.dirname(p.subPathOpt() orelse return null) orelse "", | |
| 220 | }; | |
| 221 | } | |
| 222 | ||
| 216 | 223 | pub fn basename(p: Path) []const u8 { |
| 217 | 224 | return fs.path.basename(p.sub_path); |
| 218 | 225 | } |
lib/std/Build/Configuration.zig created+3436| ... | ... | @@ -0,0 +1,3436 @@ |
| 1 | const Configuration = @This(); | |
| 2 | ||
| 3 | const std = @import("../std.zig"); | |
| 4 | const Io = std.Io; | |
| 5 | const Allocator = std.mem.Allocator; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const max_u32 = std.math.maxInt(u32); | |
| 8 | ||
| 9 | string_bytes: []u8, | |
| 10 | steps: []Step, | |
| 11 | path_deps_base: []Path.Base, | |
| 12 | path_deps_sub: []String, | |
| 13 | unlazy_deps: []String, | |
| 14 | system_integrations: []SystemIntegration, | |
| 15 | available_options: []AvailableOption, | |
| 16 | search_prefixes: []String, | |
| 17 | extra: []u32, | |
| 18 | default_step: Step.Index, | |
| 19 | generated_files_len: u32, | |
| 20 | poisoned: bool, | |
| 21 | ||
| 22 | /// The field order here matches `Configuration` which documents the order in | |
| 23 | /// the serialized format. | |
| 24 | pub const Header = extern struct { | |
| 25 | string_bytes_len: u32, | |
| 26 | steps_len: u32, | |
| 27 | path_deps_len: u32, | |
| 28 | unlazy_deps_len: u32, | |
| 29 | system_integrations_len: u32, | |
| 30 | available_options_len: u32, | |
| 31 | search_prefixes_len: u32, | |
| 32 | extra_len: u32, | |
| 33 | ||
| 34 | default_step: Step.Index, | |
| 35 | /// There is not actually any data stored for this - it just provides a way | |
| 36 | /// for maker process to preallocate an array for these. | |
| 37 | generated_files_len: u32, | |
| 38 | flags: Flags, | |
| 39 | ||
| 40 | pub const Flags = packed struct(u32) { | |
| 41 | poisoned: bool, | |
| 42 | _: u31 = 0, | |
| 43 | }; | |
| 44 | }; | |
| 45 | ||
| 46 | pub const Wip = struct { | |
| 47 | gpa: Allocator, | |
| 48 | string_table: StringTable = .empty, | |
| 49 | /// De-duplicates an array inside `extra`. | |
| 50 | dedupe_table: DedupeTable = .empty, | |
| 51 | targets_table: TargetsTable = .empty, | |
| 52 | ||
| 53 | string_bytes: std.ArrayList(u8) = .empty, | |
| 54 | unlazy_deps: std.ArrayList(String) = .empty, | |
| 55 | system_integrations: std.ArrayList(SystemIntegration) = .empty, | |
| 56 | available_options: std.ArrayList(AvailableOption) = .empty, | |
| 57 | steps: std.ArrayList(Step) = .empty, | |
| 58 | path_deps: std.MultiArrayList(Path) = .empty, | |
| 59 | search_prefixes: std.ArrayList(String) = .empty, | |
| 60 | extra: std.ArrayList(u32) = .empty, | |
| 61 | next_generated_file_index: u32 = 0, | |
| 62 | cache_poison: bool = false, | |
| 63 | ||
| 64 | const DedupeTable = std.HashMapUnmanaged(ExtraSlice, void, ExtraSlice.Context, std.hash_map.default_max_load_percentage); | |
| 65 | const TargetsTable = std.HashMapUnmanaged(TargetQuery.Index, void, TargetsTableContext, std.hash_map.default_max_load_percentage); | |
| 66 | ||
| 67 | const ExtraSlice = struct { | |
| 68 | index: u32, | |
| 69 | len: u32, | |
| 70 | ||
| 71 | const Context = struct { | |
| 72 | extra: []const u32, | |
| 73 | ||
| 74 | pub fn eql(ctx: @This(), a: ExtraSlice, b: ExtraSlice) bool { | |
| 75 | const slice_a = ctx.extra[a.index..][0..a.len]; | |
| 76 | const slice_b = ctx.extra[b.index..][0..b.len]; | |
| 77 | return std.mem.eql(u32, slice_a, slice_b); | |
| 78 | } | |
| 79 | ||
| 80 | pub fn hash(ctx: @This(), key: ExtraSlice) u64 { | |
| 81 | const slice = ctx.extra[key.index..][0..key.len]; | |
| 82 | return std.hash_map.hashString(@ptrCast(slice)); | |
| 83 | } | |
| 84 | }; | |
| 85 | }; | |
| 86 | ||
| 87 | const TargetsTableContext = struct { | |
| 88 | extra: []const u32, | |
| 89 | ||
| 90 | pub fn eql(ctx: @This(), a: TargetQuery.Index, b: TargetQuery.Index) bool { | |
| 91 | const slice_a = a.extraSlice(ctx.extra); | |
| 92 | const slice_b = b.extraSlice(ctx.extra); | |
| 93 | return std.mem.eql(u32, slice_a, slice_b); | |
| 94 | } | |
| 95 | ||
| 96 | pub fn hash(ctx: @This(), key: TargetQuery.Index) u64 { | |
| 97 | const slice = key.extraSlice(ctx.extra); | |
| 98 | return std.hash_map.hashString(@ptrCast(slice)); | |
| 99 | } | |
| 100 | }; | |
| 101 | ||
| 102 | const StringTable = std.HashMapUnmanaged(String, void, StringTableContext, std.hash_map.default_max_load_percentage); | |
| 103 | const StringTableContext = struct { | |
| 104 | bytes: []const u8, | |
| 105 | ||
| 106 | pub fn eql(_: @This(), a: String, b: String) bool { | |
| 107 | return a == b; | |
| 108 | } | |
| 109 | ||
| 110 | pub fn hash(ctx: @This(), key: String) u64 { | |
| 111 | return std.hash_map.hashString(std.mem.sliceTo(ctx.bytes[@intFromEnum(key)..], 0)); | |
| 112 | } | |
| 113 | }; | |
| 114 | ||
| 115 | const StringTableIndexAdapter = struct { | |
| 116 | bytes: []const u8, | |
| 117 | ||
| 118 | pub fn eql(ctx: @This(), a: []const u8, b: String) bool { | |
| 119 | return std.mem.eql(u8, a, std.mem.sliceTo(ctx.bytes[@intFromEnum(b)..], 0)); | |
| 120 | } | |
| 121 | ||
| 122 | pub fn hash(_: @This(), adapted_key: []const u8) u64 { | |
| 123 | assert(std.mem.indexOfScalar(u8, adapted_key, 0) == null); | |
| 124 | return std.hash_map.hashString(adapted_key); | |
| 125 | } | |
| 126 | }; | |
| 127 | ||
| 128 | pub fn init(gpa: Allocator) Wip { | |
| 129 | return .{ .gpa = gpa }; | |
| 130 | } | |
| 131 | ||
| 132 | pub fn deinit(wip: *Wip) void { | |
| 133 | const gpa = wip.gpa; | |
| 134 | wip.string_bytes.deinit(gpa); | |
| 135 | wip.unlazy_deps.deinit(gpa); | |
| 136 | wip.system_integrations.deinit(gpa); | |
| 137 | wip.available_options.deinit(gpa); | |
| 138 | wip.steps.deinit(gpa); | |
| 139 | wip.path_deps.deinit(gpa); | |
| 140 | wip.search_prefixes.deinit(gpa); | |
| 141 | wip.extra.deinit(gpa); | |
| 142 | wip.* = undefined; | |
| 143 | } | |
| 144 | ||
| 145 | pub const Static = struct { | |
| 146 | default_step: Step.Index, | |
| 147 | generated_files_len: u32, | |
| 148 | poisoned: bool, | |
| 149 | }; | |
| 150 | ||
| 151 | pub fn write(wip: *Wip, w: *Io.Writer, static: Static) Io.Writer.Error!void { | |
| 152 | const header: Header = .{ | |
| 153 | .string_bytes_len = @intCast(wip.string_bytes.items.len), | |
| 154 | .steps_len = @intCast(wip.steps.items.len), | |
| 155 | .path_deps_len = @intCast(wip.path_deps.len), | |
| 156 | .unlazy_deps_len = @intCast(wip.unlazy_deps.items.len), | |
| 157 | .system_integrations_len = @intCast(wip.system_integrations.items.len), | |
| 158 | .available_options_len = @intCast(wip.available_options.items.len), | |
| 159 | .search_prefixes_len = @intCast(wip.search_prefixes.items.len), | |
| 160 | .extra_len = @intCast(wip.extra.items.len), | |
| 161 | ||
| 162 | .default_step = static.default_step, | |
| 163 | .generated_files_len = static.generated_files_len, | |
| 164 | .flags = .{ | |
| 165 | .poisoned = static.poisoned, | |
| 166 | }, | |
| 167 | }; | |
| 168 | var buffers = [_][]const u8{ | |
| 169 | @ptrCast(&header), | |
| 170 | wip.string_bytes.items, | |
| 171 | @ptrCast(wip.steps.items), | |
| 172 | @ptrCast(wip.path_deps.items(.base)), | |
| 173 | @ptrCast(wip.path_deps.items(.sub)), | |
| 174 | @ptrCast(wip.unlazy_deps.items), | |
| 175 | @ptrCast(wip.system_integrations.items), | |
| 176 | @ptrCast(wip.available_options.items), | |
| 177 | @ptrCast(wip.search_prefixes.items), | |
| 178 | @ptrCast(wip.extra.items), | |
| 179 | }; | |
| 180 | try w.writeVecAll(&buffers); | |
| 181 | } | |
| 182 | ||
| 183 | pub fn addString(wip: *Wip, bytes: []const u8) Allocator.Error!String { | |
| 184 | const gpa = wip.gpa; | |
| 185 | assert(std.mem.indexOfScalar(u8, bytes, 0) == null); | |
| 186 | const gop = try wip.string_table.getOrPutContextAdapted( | |
| 187 | gpa, | |
| 188 | @as([]const u8, bytes), | |
| 189 | @as(StringTableIndexAdapter, .{ .bytes = wip.string_bytes.items }), | |
| 190 | @as(StringTableContext, .{ .bytes = wip.string_bytes.items }), | |
| 191 | ); | |
| 192 | if (gop.found_existing) return gop.key_ptr.*; | |
| 193 | ||
| 194 | try wip.string_bytes.ensureUnusedCapacity(gpa, bytes.len + 1); | |
| 195 | const new_off: String = @enumFromInt(wip.string_bytes.items.len); | |
| 196 | ||
| 197 | wip.string_bytes.appendSliceAssumeCapacity(bytes); | |
| 198 | wip.string_bytes.appendAssumeCapacity(0); | |
| 199 | ||
| 200 | gop.key_ptr.* = new_off; | |
| 201 | ||
| 202 | return new_off; | |
| 203 | } | |
| 204 | ||
| 205 | pub fn addOptionalString(wip: *Wip, bytes: ?[]const u8) Allocator.Error!OptionalString { | |
| 206 | return .init(try addString(wip, bytes orelse return .none)); | |
| 207 | } | |
| 208 | ||
| 209 | pub fn addStringList(wip: *Wip, list: []const []const u8) Allocator.Error!StringList { | |
| 210 | // Increase size of extra to support the list. Add the string list | |
| 211 | // there. Then check for duplicate, reverting list if already found. | |
| 212 | const gpa = wip.gpa; | |
| 213 | const revert_index: u32 = @intCast(wip.extra.items.len); | |
| 214 | const added = try wip.extra.addManyAsSlice(gpa, list.len + 1); | |
| 215 | added[0] = @intCast(list.len); | |
| 216 | for (added[1..], list) |*d, s| d.* = @intFromEnum(try addString(wip, s)); | |
| 217 | const gop = try wip.dedupe_table.getOrPutContext(gpa, .{ | |
| 218 | .index = revert_index, | |
| 219 | .len = @intCast(added.len), | |
| 220 | }, @as(ExtraSlice.Context, .{ .extra = wip.extra.items })); | |
| 221 | ||
| 222 | if (gop.found_existing) { | |
| 223 | wip.extra.items.len = revert_index; | |
| 224 | return @enumFromInt(gop.key_ptr.index); | |
| 225 | } | |
| 226 | ||
| 227 | return @enumFromInt(revert_index); | |
| 228 | } | |
| 229 | ||
| 230 | pub fn addBytes(wip: *Wip, bytes: []const u8) Allocator.Error!Bytes { | |
| 231 | try wip.string_bytes.appendSlice(wip.gpa, bytes); | |
| 232 | return .{ | |
| 233 | .index = @intCast(wip.string_bytes.items.len - bytes.len), | |
| 234 | .len = @intCast(bytes.len), | |
| 235 | }; | |
| 236 | } | |
| 237 | ||
| 238 | pub fn addSemVer(wip: *Wip, sv: std.SemanticVersion) Allocator.Error!String { | |
| 239 | var buffer: [256]u8 = undefined; | |
| 240 | var writer: std.Io.Writer = .fixed(&buffer); | |
| 241 | sv.format(&writer) catch return error.OutOfMemory; | |
| 242 | return addString(wip, writer.buffered()); | |
| 243 | } | |
| 244 | ||
| 245 | pub fn addTargetQuery(wip: *Wip, q: *const std.Target.Query) !TargetQuery.OptionalIndex { | |
| 246 | if (q.isNative()) return .none; | |
| 247 | const gpa = wip.gpa; | |
| 248 | const cpu_name: ?String = switch (q.cpu_model) { | |
| 249 | .native, .baseline, .determined_by_arch_os => null, | |
| 250 | .explicit => |model| try wip.addString(model.name), | |
| 251 | }; | |
| 252 | const os_version_min: TargetQuery.OsVersion = if (q.os_version_min) |ver| switch (ver) { | |
| 253 | .none => .none, | |
| 254 | .semver => |sem_ver| .{ .semver = try wip.addSemVer(sem_ver) }, | |
| 255 | .windows => |win_ver| .{ .windows = win_ver }, | |
| 256 | } else .default; | |
| 257 | const os_version_max: TargetQuery.OsVersion = if (q.os_version_max) |ver| switch (ver) { | |
| 258 | .none => .none, | |
| 259 | .semver => |sem_ver| .{ .semver = try wip.addSemVer(sem_ver) }, | |
| 260 | .windows => |win_ver| .{ .windows = win_ver }, | |
| 261 | } else .default; | |
| 262 | const glibc_version: ?String = if (q.glibc_version) |sem_ver| try wip.addSemVer(sem_ver) else null; | |
| 263 | const dynamic_linker: ?String = if (q.dynamic_linker) |*dl| | |
| 264 | if (dl.get()) |s| try wip.addString(s) else .empty | |
| 265 | else | |
| 266 | null; | |
| 267 | const cpu_features_add_empty = q.cpu_features_add.isEmpty(); | |
| 268 | const cpu_features_sub_empty = q.cpu_features_sub.isEmpty(); | |
| 269 | const result_index: TargetQuery.Index = try wip.addExtra(TargetQuery, .{ | |
| 270 | .flags = .{ | |
| 271 | .cpu_arch = .init(q.cpu_arch), | |
| 272 | .cpu_model = .init(q.cpu_model), | |
| 273 | .cpu_features_add = !cpu_features_add_empty, | |
| 274 | .cpu_features_sub = !cpu_features_sub_empty, | |
| 275 | .os_tag = .init(q.os_tag), | |
| 276 | .abi = .init(q.abi), | |
| 277 | .object_format = .init(q.ofmt), | |
| 278 | .os_version_min = os_version_min, | |
| 279 | .os_version_max = os_version_max, | |
| 280 | .glibc_version = glibc_version != null, | |
| 281 | .android_api_level = q.android_api_level != null, | |
| 282 | .dynamic_linker = dynamic_linker != null, | |
| 283 | }, | |
| 284 | .cpu_features_add = .{ .value = if (cpu_features_add_empty) null else q.cpu_features_add }, | |
| 285 | .cpu_features_sub = .{ .value = if (cpu_features_sub_empty) null else q.cpu_features_sub }, | |
| 286 | .glibc_version = .{ .value = glibc_version }, | |
| 287 | .android_api_level = .{ .value = q.android_api_level }, | |
| 288 | .dynamic_linker = .{ .value = dynamic_linker }, | |
| 289 | .cpu_name = .{ .value = cpu_name }, | |
| 290 | .os_version_min = .{ .u = os_version_min }, | |
| 291 | .os_version_max = .{ .u = os_version_max }, | |
| 292 | }); | |
| 293 | ||
| 294 | // Deduplicate. | |
| 295 | const gop = try wip.targets_table.getOrPutContext(gpa, result_index, @as(TargetsTableContext, .{ | |
| 296 | .extra = wip.extra.items, | |
| 297 | })); | |
| 298 | if (gop.found_existing) { | |
| 299 | wip.extra.items.len = @intFromEnum(result_index); | |
| 300 | return .init(gop.key_ptr.*); | |
| 301 | } else { | |
| 302 | return .init(result_index); | |
| 303 | } | |
| 304 | } | |
| 305 | ||
| 306 | pub fn addTarget(wip: *Wip, t: std.Target) !TargetQuery.Index { | |
| 307 | const gpa = wip.gpa; | |
| 308 | const cpu_name: String = try wip.addString(t.cpu.model.name); | |
| 309 | ||
| 310 | const os_version_min: TargetQuery.OsVersion, const os_version_max: TargetQuery.OsVersion, const glibc_version: ?String, const android_api_level: ?u32 = switch (t.os.versionRange()) { | |
| 311 | .none => .{ | |
| 312 | .none, | |
| 313 | .none, | |
| 314 | null, | |
| 315 | null, | |
| 316 | }, | |
| 317 | .semver => |range| .{ | |
| 318 | .{ .semver = try wip.addSemVer(range.min) }, | |
| 319 | .{ .semver = try wip.addSemVer(range.max) }, | |
| 320 | null, | |
| 321 | null, | |
| 322 | }, | |
| 323 | .hurd => |hurd| .{ | |
| 324 | .{ .semver = try wip.addSemVer(hurd.range.min) }, | |
| 325 | .{ .semver = try wip.addSemVer(hurd.range.max) }, | |
| 326 | try wip.addSemVer(hurd.glibc), | |
| 327 | null, | |
| 328 | }, | |
| 329 | .linux => |linux| .{ | |
| 330 | .{ .semver = try wip.addSemVer(linux.range.min) }, | |
| 331 | .{ .semver = try wip.addSemVer(linux.range.max) }, | |
| 332 | try wip.addSemVer(linux.glibc), | |
| 333 | linux.android, | |
| 334 | }, | |
| 335 | .windows => |range| .{ | |
| 336 | .{ .windows = range.min }, | |
| 337 | .{ .windows = range.max }, | |
| 338 | null, | |
| 339 | null, | |
| 340 | }, | |
| 341 | }; | |
| 342 | const dynamic_linker: ?String = if (t.dynamic_linker.get()) |dl| try wip.addString(dl) else null; | |
| 343 | const cpu_features_add_empty = t.cpu.features.isEmpty(); | |
| 344 | const result_index = try wip.addExtra(TargetQuery, .{ | |
| 345 | .flags = .{ | |
| 346 | .cpu_arch = .init(t.cpu.arch), | |
| 347 | .cpu_model = .explicit, | |
| 348 | .cpu_features_add = !cpu_features_add_empty, | |
| 349 | .cpu_features_sub = false, | |
| 350 | .os_tag = .init(t.os.tag), | |
| 351 | .abi = .init(t.abi), | |
| 352 | .object_format = .init(t.ofmt), | |
| 353 | .os_version_min = os_version_min, | |
| 354 | .os_version_max = os_version_max, | |
| 355 | .glibc_version = glibc_version != null, | |
| 356 | .android_api_level = android_api_level != null, | |
| 357 | .dynamic_linker = dynamic_linker != null, | |
| 358 | }, | |
| 359 | .cpu_features_add = .{ .value = if (cpu_features_add_empty) null else t.cpu.features }, | |
| 360 | .cpu_features_sub = .{ .value = null }, | |
| 361 | .glibc_version = .{ .value = glibc_version }, | |
| 362 | .android_api_level = .{ .value = android_api_level }, | |
| 363 | .dynamic_linker = .{ .value = dynamic_linker }, | |
| 364 | .cpu_name = .{ .value = cpu_name }, | |
| 365 | .os_version_min = .{ .u = os_version_min }, | |
| 366 | .os_version_max = .{ .u = os_version_max }, | |
| 367 | }); | |
| 368 | ||
| 369 | // Deduplicate. | |
| 370 | const gop = try wip.targets_table.getOrPutContext(gpa, result_index, @as(TargetsTableContext, .{ | |
| 371 | .extra = wip.extra.items, | |
| 372 | })); | |
| 373 | if (gop.found_existing) { | |
| 374 | wip.extra.items.len = @intFromEnum(result_index); | |
| 375 | return gop.key_ptr.*; | |
| 376 | } else { | |
| 377 | return result_index; | |
| 378 | } | |
| 379 | } | |
| 380 | ||
| 381 | pub fn addExtra(wip: *Wip, comptime T: type, v: T) Allocator.Error!T.Index { | |
| 382 | const extra_len = Storage.extraLen(v); | |
| 383 | try wip.extra.ensureUnusedCapacity(wip.gpa, extra_len); | |
| 384 | return addExtraReserved(wip, T, v); | |
| 385 | } | |
| 386 | ||
| 387 | pub fn addExtraErased(wip: *Wip, comptime T: type, v: T) Allocator.Error!u32 { | |
| 388 | const extra_len = Storage.extraLen(v); | |
| 389 | try wip.extra.ensureUnusedCapacity(wip.gpa, extra_len); | |
| 390 | return addExtraReservedErased(wip, T, v); | |
| 391 | } | |
| 392 | ||
| 393 | /// Same as `addExtra` but uses a hash map to possibly return an already | |
| 394 | /// existing index instead of appending to `extra`. | |
| 395 | pub fn addDeduped(wip: *Wip, comptime T: type, v: T) Allocator.Error!T.Index { | |
| 396 | const gpa = wip.gpa; | |
| 397 | const revert_index = wip.extra.items.len; | |
| 398 | const upper_bound_len = Storage.extraLen(v); | |
| 399 | try wip.extra.ensureUnusedCapacity(gpa, upper_bound_len); | |
| 400 | try wip.dedupe_table.ensureUnusedCapacityContext(gpa, 1, @as(ExtraSlice.Context, .{ | |
| 401 | .extra = wip.extra.items, | |
| 402 | })); | |
| 403 | const new_index = addExtraReservedErased(wip, T, v); | |
| 404 | const len: u32 = @intCast(wip.extra.items.len - new_index); | |
| 405 | assert(len != 0); | |
| 406 | const gop = wip.dedupe_table.getOrPutAssumeCapacityContext(.{ | |
| 407 | .index = new_index, | |
| 408 | .len = len, | |
| 409 | }, @as(ExtraSlice.Context, .{ .extra = wip.extra.items })); | |
| 410 | ||
| 411 | if (gop.found_existing) { | |
| 412 | wip.extra.items.len = revert_index; | |
| 413 | return @enumFromInt(gop.key_ptr.index); | |
| 414 | } | |
| 415 | ||
| 416 | return @enumFromInt(new_index); | |
| 417 | } | |
| 418 | ||
| 419 | pub fn addExtraReserved(wip: *Wip, comptime T: type, v: T) T.Index { | |
| 420 | return @enumFromInt(addExtraReservedErased(wip, T, v)); | |
| 421 | } | |
| 422 | ||
| 423 | pub fn addExtraReservedErased(wip: *Wip, comptime T: type, v: T) u32 { | |
| 424 | const result: u32 = @intCast(wip.extra.items.len); | |
| 425 | wip.extra.items.len = Storage.setExtra(wip.extra.allocatedSlice(), result, v); | |
| 426 | return result; | |
| 427 | } | |
| 428 | ||
| 429 | fn addExtraOptionalStringAssumeCapacity(wip: *Wip, optional_string: ?String) void { | |
| 430 | const string = optional_string orelse return; | |
| 431 | wip.extra.appendAssumeCapacity(@intFromEnum(string)); | |
| 432 | } | |
| 433 | ||
| 434 | pub fn addGeneratedFile(wip: *Wip) GeneratedFileIndex { | |
| 435 | defer wip.next_generated_file_index += 1; | |
| 436 | return @enumFromInt(wip.next_generated_file_index); | |
| 437 | } | |
| 438 | ||
| 439 | /// Returned slice expires upon next append to the configuration. | |
| 440 | pub fn stringSlice(wip: *const Wip, s: String) [:0]const u8 { | |
| 441 | const start_slice = wip.string_bytes.items[@intFromEnum(s)..]; | |
| 442 | return start_slice[0..std.mem.indexOfScalar(u8, start_slice, 0).? :0]; | |
| 443 | } | |
| 444 | }; | |
| 445 | ||
| 446 | pub const SystemIntegration = extern struct { | |
| 447 | name: String, | |
| 448 | status: Status, | |
| 449 | ||
| 450 | pub const Status = enum(u32) { | |
| 451 | disabled = 0, | |
| 452 | enabled = 1, | |
| 453 | }; | |
| 454 | }; | |
| 455 | ||
| 456 | pub const AvailableOption = extern struct { | |
| 457 | name: String, | |
| 458 | description: String, | |
| 459 | type: Type, | |
| 460 | /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options | |
| 461 | enum_options: OptionalStringList, | |
| 462 | ||
| 463 | pub const Type = enum(u8) { | |
| 464 | bool, | |
| 465 | int, | |
| 466 | float, | |
| 467 | @"enum", | |
| 468 | enum_list, | |
| 469 | string, | |
| 470 | list, | |
| 471 | build_id, | |
| 472 | lazy_path, | |
| 473 | lazy_path_list, | |
| 474 | }; | |
| 475 | }; | |
| 476 | ||
| 477 | pub const Step = extern struct { | |
| 478 | name: String, | |
| 479 | owner: Package.Index, | |
| 480 | deps: Deps.Index, | |
| 481 | max_rss: MaxRss, | |
| 482 | extended: Storage.Extended(Flags, union(Tag) { | |
| 483 | check_file: CheckFile, | |
| 484 | compile: Compile, | |
| 485 | config_header: ConfigHeader, | |
| 486 | fail: Fail, | |
| 487 | find_program: FindProgram, | |
| 488 | fmt: Fmt, | |
| 489 | install_artifact: InstallArtifact, | |
| 490 | install_dir: InstallDir, | |
| 491 | install_file: InstallFile, | |
| 492 | obj_copy: ObjCopy, | |
| 493 | options: Options, | |
| 494 | run: Run, | |
| 495 | top_level: TopLevel, | |
| 496 | translate_c: TranslateC, | |
| 497 | update_source_files: UpdateSourceFiles, | |
| 498 | write_file: WriteFile, | |
| 499 | }), | |
| 500 | ||
| 501 | /// Points into `steps`. | |
| 502 | pub const Index = enum(u32) { | |
| 503 | _, | |
| 504 | ||
| 505 | pub fn ptr(i: Index, c: *const Configuration) *const Step { | |
| 506 | return &c.steps[@intFromEnum(i)]; | |
| 507 | } | |
| 508 | }; | |
| 509 | ||
| 510 | /// Shared by all steps. | |
| 511 | pub const Flags = packed struct(u32) { | |
| 512 | tag: Tag, | |
| 513 | _: u27 = 0, | |
| 514 | }; | |
| 515 | ||
| 516 | pub const Tag = enum(u5) { | |
| 517 | check_file, | |
| 518 | compile, | |
| 519 | config_header, | |
| 520 | fail, | |
| 521 | find_program, | |
| 522 | fmt, | |
| 523 | install_artifact, | |
| 524 | install_dir, | |
| 525 | install_file, | |
| 526 | obj_copy, | |
| 527 | options, | |
| 528 | run, | |
| 529 | top_level, | |
| 530 | translate_c, | |
| 531 | update_source_files, | |
| 532 | write_file, | |
| 533 | }; | |
| 534 | ||
| 535 | pub const TopLevel = struct { | |
| 536 | flags: @This().Flags = .{}, | |
| 537 | description: String, | |
| 538 | ||
| 539 | pub const Flags = packed struct(u32) { | |
| 540 | tag: Tag = .top_level, | |
| 541 | _: u27 = 0, | |
| 542 | }; | |
| 543 | }; | |
| 544 | ||
| 545 | /// The first dependency step index will be the compile step whose | |
| 546 | /// artifacts are being installed with this step. | |
| 547 | pub const InstallArtifact = struct { | |
| 548 | flags: @This().Flags, | |
| 549 | bin_dir: Storage.FlagOptional(.flags, .bin_dir, InstallDestDir), | |
| 550 | implib_dir: Storage.FlagOptional(.flags, .implib_dir, InstallDestDir), | |
| 551 | pdb_dir: Storage.FlagOptional(.flags, .pdb_dir, InstallDestDir), | |
| 552 | h_dir: Storage.FlagOptional(.flags, .h_dir, InstallDestDir), | |
| 553 | bin_sub_path: Storage.FlagOptional(.flags, .bin_sub_path, String), | |
| 554 | ||
| 555 | pub const Flags = packed struct(u32) { | |
| 556 | tag: Tag = .install_artifact, | |
| 557 | dylib_symlinks: bool, | |
| 558 | bin_dir: bool, | |
| 559 | implib_dir: bool, | |
| 560 | pdb_dir: bool, | |
| 561 | h_dir: bool, | |
| 562 | bin_sub_path: bool, | |
| 563 | _: u21 = 0, | |
| 564 | }; | |
| 565 | }; | |
| 566 | ||
| 567 | pub const Run = struct { | |
| 568 | flags: @This().Flags, | |
| 569 | flags2: Flags2, | |
| 570 | args: Storage.LengthPrefixedList(Arg.Index), | |
| 571 | cwd: Storage.FlagOptional(.flags, .cwd, LazyPath.Index), | |
| 572 | captured_stdout: Storage.FlagOptional(.flags, .captured_stdout, CapturedStream), | |
| 573 | captured_stderr: Storage.FlagOptional(.flags, .captured_stderr, CapturedStream), | |
| 574 | file_inputs: Storage.LengthPrefixedList(LazyPath.Index), | |
| 575 | stdio_limit: Storage.FlagOptional(.flags, .stdio_limit, u64), | |
| 576 | /// Always a compile step. | |
| 577 | producer: Storage.FlagOptional(.flags, .producer, Step.Index), | |
| 578 | /// First half is keys, second half is values. | |
| 579 | environ_map: Storage.FlagOptional(.flags, .environ_map, EnvironMap.Index), | |
| 580 | stdin: Storage.FlagUnion(.flags, .stdin, StdIn), | |
| 581 | expect_stderr_exact: Storage.FlagOptional(.flags2, .expect_stderr_exact, Bytes), | |
| 582 | expect_stdout_exact: Storage.FlagOptional(.flags2, .expect_stdout_exact, Bytes), | |
| 583 | expect_stderr_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stderr_match, Bytes), | |
| 584 | expect_stdout_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stdout_match, Bytes), | |
| 585 | expect_term_value: Storage.FlagOptional(.flags2, .expect_term, u32), | |
| 586 | ||
| 587 | pub const CapturedStream = extern struct { | |
| 588 | generated_file: GeneratedFileIndex, | |
| 589 | basename: String, | |
| 590 | }; | |
| 591 | ||
| 592 | pub const Arg = struct { | |
| 593 | flags: @This().Flags, | |
| 594 | prefix: Storage.FlagOptional(.flags, .prefix, String), | |
| 595 | suffix: Storage.FlagOptional(.flags, .suffix, String), | |
| 596 | basename: Storage.FlagOptional(.flags, .basename, String), | |
| 597 | path: Storage.FlagOptional(.flags, .path, LazyPath.Index), | |
| 598 | /// Always a compile step. | |
| 599 | producer: Storage.FlagOptional(.flags, .producer, Step.Index), | |
| 600 | generated: Storage.FlagOptional(.flags, .generated, GeneratedFileIndex), | |
| 601 | ||
| 602 | pub const Flags = packed struct(u32) { | |
| 603 | tag: Arg.Tag, | |
| 604 | prefix: bool, | |
| 605 | suffix: bool, | |
| 606 | basename: bool, | |
| 607 | path: bool, | |
| 608 | producer: bool, | |
| 609 | generated: bool, | |
| 610 | dep_file: bool, | |
| 611 | _: u21 = 0, | |
| 612 | }; | |
| 613 | ||
| 614 | pub const Tag = enum(u4) { | |
| 615 | artifact, | |
| 616 | /// `path` contains the file. | |
| 617 | path_file, | |
| 618 | path_directory, | |
| 619 | /// `prefix` contains the string. | |
| 620 | string, | |
| 621 | file_content, | |
| 622 | output_file, | |
| 623 | output_directory, | |
| 624 | passthru, | |
| 625 | }; | |
| 626 | ||
| 627 | pub const Index = IndexType(@This()); | |
| 628 | }; | |
| 629 | ||
| 630 | pub const Color = enum(u4) { | |
| 631 | /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset. | |
| 632 | enable, | |
| 633 | /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset. | |
| 634 | disable, | |
| 635 | /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`. | |
| 636 | inherit, | |
| 637 | /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`. | |
| 638 | auto, | |
| 639 | /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables. | |
| 640 | /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`. | |
| 641 | manual, | |
| 642 | }; | |
| 643 | ||
| 644 | pub const StdIn = union(@This().Tag) { | |
| 645 | none: void, | |
| 646 | bytes: Bytes, | |
| 647 | lazy_path: LazyPath.Index, | |
| 648 | ||
| 649 | pub const Tag = enum(u2) { none, bytes, lazy_path }; | |
| 650 | }; | |
| 651 | pub const TrimWhitespace = enum(u2) { none, all, leading, trailing }; | |
| 652 | pub const StdIo = enum(u2) { infer_from_args, inherit, check, zig_test }; | |
| 653 | ||
| 654 | pub const ExpectTermStatus = enum(u2) { exited, signal, stopped, unknown }; | |
| 655 | ||
| 656 | pub const Flags = packed struct(u32) { | |
| 657 | tag: Tag = .run, | |
| 658 | disable_zig_progress: bool, | |
| 659 | skip_foreign_checks: bool, | |
| 660 | failing_to_execute_foreign_is_an_error: bool, | |
| 661 | has_side_effects: bool, | |
| 662 | test_runner_mode: bool, | |
| 663 | color: Color, | |
| 664 | stdin: StdIn.Tag, | |
| 665 | stdio: StdIo, | |
| 666 | stdout_trim_whitespace: TrimWhitespace, | |
| 667 | stderr_trim_whitespace: TrimWhitespace, | |
| 668 | stdio_limit: bool, | |
| 669 | producer: bool, | |
| 670 | cwd: bool, | |
| 671 | captured_stdout: bool, | |
| 672 | captured_stderr: bool, | |
| 673 | environ_map: bool, | |
| 674 | _: u4 = 0, | |
| 675 | }; | |
| 676 | ||
| 677 | pub const Flags2 = packed struct(u32) { | |
| 678 | expect_stderr_exact: bool, | |
| 679 | expect_stdout_exact: bool, | |
| 680 | expect_stderr_match: bool, | |
| 681 | expect_stdout_match: bool, | |
| 682 | expect_term: bool, | |
| 683 | expect_term_status: ExpectTermStatus, | |
| 684 | _: u25 = 0, | |
| 685 | }; | |
| 686 | }; | |
| 687 | ||
| 688 | pub const Compile = struct { | |
| 689 | flags: @This().Flags, | |
| 690 | flags2: Flags2, | |
| 691 | flags3: Flags3, | |
| 692 | flags4: Flags4, | |
| 693 | ||
| 694 | root_module: Module.Index, | |
| 695 | root_name: String, | |
| 696 | ||
| 697 | filters: Storage.FlagLengthPrefixedList(.flags, .filters_len, String), | |
| 698 | exec_cmd_args: Storage.FlagLengthPrefixedList(.flags, .exec_cmd_args_len, OptionalString), | |
| 699 | installed_headers: Storage.FlagLengthPrefixedList(.flags, .installed_headers_len, Storage.Extended(InstalledHeader.Flags, InstalledHeader)), | |
| 700 | force_undefined_symbols: Storage.FlagLengthPrefixedList(.flags, .force_undefined_symbols_len, String), | |
| 701 | expect_errors: Storage.FlagUnion(.flags4, .expect_errors, ExpectErrors), | |
| 702 | linker_script: Storage.FlagOptional(.flags4, .linker_script, LazyPath.Index), | |
| 703 | version_script: Storage.FlagOptional(.flags4, .version_script, LazyPath.Index), | |
| 704 | zig_lib_dir: Storage.FlagOptional(.flags3, .zig_lib_dir, LazyPath.Index), | |
| 705 | libc_file: Storage.FlagOptional(.flags4, .libc_file, LazyPath.Index), | |
| 706 | win32_manifest: Storage.FlagOptional(.flags3, .win32_manifest, LazyPath.Index), | |
| 707 | win32_module_definition: Storage.FlagOptional(.flags3, .win32_module_definition, LazyPath.Index), | |
| 708 | entitlements: Storage.FlagOptional(.flags4, .entitlements, LazyPath.Index), | |
| 709 | version: Storage.FlagOptional(.flags3, .version, String), // semantic version string | |
| 710 | entry: Storage.EnumOptional(.flags3, .entry, .symbol_name, String), | |
| 711 | install_name: Storage.FlagOptional(.flags4, .install_name, String), | |
| 712 | initial_memory: Storage.FlagOptional(.flags3, .initial_memory, u64), | |
| 713 | max_memory: Storage.FlagOptional(.flags3, .max_memory, u64), | |
| 714 | global_base: Storage.FlagOptional(.flags3, .global_base, u64), | |
| 715 | image_base: Storage.FlagOptional(.flags3, .image_base, u64), | |
| 716 | link_z_common_page_size: Storage.FlagOptional(.flags4, .link_z_common_page_size, u64), | |
| 717 | link_z_max_page_size: Storage.FlagOptional(.flags4, .link_z_max_page_size, u64), | |
| 718 | pagezero_size: Storage.FlagOptional(.flags4, .pagezero_size, u64), | |
| 719 | stack_size: Storage.FlagOptional(.flags4, .stack_size, u64), | |
| 720 | headerpad_size: Storage.FlagOptional(.flags4, .headerpad_size, u32), | |
| 721 | error_limit: Storage.FlagOptional(.flags4, .error_limit, u32), | |
| 722 | build_id: Storage.EnumOptional(.flags3, .build_id, .hexstring, String), | |
| 723 | test_runner: Storage.FlagUnion(.flags3, .test_runner, TestRunner), | |
| 724 | ||
| 725 | emit_directory: Storage.FlagOptional(.flags4, .emit_directory, GeneratedFileIndex), | |
| 726 | generated_docs: Storage.FlagOptional(.flags4, .generated_docs, GeneratedFileIndex), | |
| 727 | generated_asm: Storage.FlagOptional(.flags4, .generated_asm, GeneratedFileIndex), | |
| 728 | generated_bin: Storage.FlagOptional(.flags4, .generated_bin, GeneratedFileIndex), | |
| 729 | generated_pdb: Storage.FlagOptional(.flags4, .generated_pdb, GeneratedFileIndex), | |
| 730 | generated_implib: Storage.FlagOptional(.flags4, .generated_implib, GeneratedFileIndex), | |
| 731 | generated_llvm_bc: Storage.FlagOptional(.flags4, .generated_llvm_bc, GeneratedFileIndex), | |
| 732 | generated_llvm_ir: Storage.FlagOptional(.flags4, .generated_llvm_ir, GeneratedFileIndex), | |
| 733 | generated_h: Storage.FlagOptional(.flags4, .generated_h, GeneratedFileIndex), | |
| 734 | ||
| 735 | pub const InstalledHeader = union(@This().Tag) { | |
| 736 | file: File, | |
| 737 | directory: Directory, | |
| 738 | ||
| 739 | pub const Flags = packed struct(u32) { | |
| 740 | tag: InstalledHeader.Tag, | |
| 741 | _: u24 = 0, | |
| 742 | }; | |
| 743 | ||
| 744 | pub const Tag = enum(u8) { | |
| 745 | file, | |
| 746 | directory, | |
| 747 | }; | |
| 748 | ||
| 749 | pub const File = struct { | |
| 750 | flags: @This().Flags = .{}, | |
| 751 | source: LazyPath.Index, | |
| 752 | dest_sub_path: String, | |
| 753 | ||
| 754 | pub const Flags = packed struct(u32) { | |
| 755 | tag: InstalledHeader.Tag = .file, | |
| 756 | _: u24 = 0, | |
| 757 | }; | |
| 758 | }; | |
| 759 | ||
| 760 | pub const Directory = struct { | |
| 761 | flags: @This().Flags, | |
| 762 | source: LazyPath.Index, | |
| 763 | dest_sub_path: String, | |
| 764 | exclude_extensions: Storage.FlagLengthPrefixedList(.flags, .exclude_extensions, String), | |
| 765 | include_extensions: Storage.FlagLengthPrefixedList(.flags, .include_extensions, String), | |
| 766 | ||
| 767 | pub const Flags = packed struct(u32) { | |
| 768 | tag: InstalledHeader.Tag = .directory, | |
| 769 | exclude_extensions: bool, | |
| 770 | include_extensions: bool, | |
| 771 | _: u22 = 0, | |
| 772 | }; | |
| 773 | }; | |
| 774 | }; | |
| 775 | pub const ExpectErrors = union(@This().Tag) { | |
| 776 | pub const Tag = enum(u3) { contains, exact, starts_with, stderr_contains, none }; | |
| 777 | ||
| 778 | contains: String, | |
| 779 | exact: Storage.LengthPrefixedList(String), | |
| 780 | starts_with: String, | |
| 781 | stderr_contains: String, | |
| 782 | none: void, | |
| 783 | }; | |
| 784 | pub const TestRunner = union(@This().Tag) { | |
| 785 | pub const Tag = enum(u2) { default, simple, server }; | |
| 786 | ||
| 787 | default: void, | |
| 788 | simple: LazyPath.Index, | |
| 789 | server: LazyPath.Index, | |
| 790 | }; | |
| 791 | pub const Entry = enum(u2) { default, disabled, enabled, symbol_name }; | |
| 792 | ||
| 793 | pub const Lto = enum(u2) { | |
| 794 | none, | |
| 795 | full, | |
| 796 | thin, | |
| 797 | default, | |
| 798 | ||
| 799 | pub fn init(lto: ?std.zig.LtoMode) Lto { | |
| 800 | return switch (lto orelse return .default) { | |
| 801 | .none => .none, | |
| 802 | .full => .full, | |
| 803 | .thin => .thin, | |
| 804 | }; | |
| 805 | } | |
| 806 | }; | |
| 807 | ||
| 808 | pub const BuildId = enum(u3) { | |
| 809 | none, | |
| 810 | fast, | |
| 811 | uuid, | |
| 812 | sha1, | |
| 813 | md5, | |
| 814 | hexstring, | |
| 815 | default, | |
| 816 | ||
| 817 | pub fn init(build_id: ?std.zig.BuildId) BuildId { | |
| 818 | return switch (build_id orelse return .default) { | |
| 819 | .none => .none, | |
| 820 | .fast => .fast, | |
| 821 | .uuid => .uuid, | |
| 822 | .sha1 => .sha1, | |
| 823 | .md5 => .md5, | |
| 824 | .hexstring => .hexstring, | |
| 825 | }; | |
| 826 | } | |
| 827 | ||
| 828 | pub fn unwrap(this: @This(), hexstring: ?String, c: *const Configuration) ?std.zig.BuildId { | |
| 829 | if (hexstring) |h| { | |
| 830 | assert(this == .hexstring); | |
| 831 | return .initHexString(h.slice(c)); | |
| 832 | } | |
| 833 | return switch (this) { | |
| 834 | .none => .none, | |
| 835 | .fast => .fast, | |
| 836 | .uuid => .uuid, | |
| 837 | .sha1 => .sha1, | |
| 838 | .md5 => .md5, | |
| 839 | .hexstring => unreachable, | |
| 840 | .default => null, | |
| 841 | }; | |
| 842 | } | |
| 843 | }; | |
| 844 | pub const WasiExecModel = enum(u2) { | |
| 845 | default, | |
| 846 | command, | |
| 847 | reactor, | |
| 848 | ||
| 849 | pub fn init(wasi_exec_model: ?std.builtin.WasiExecModel) WasiExecModel { | |
| 850 | return switch (wasi_exec_model orelse return .default) { | |
| 851 | .command => .command, | |
| 852 | .reactor => .reactor, | |
| 853 | }; | |
| 854 | } | |
| 855 | }; | |
| 856 | pub const Linkage = enum(u2) { | |
| 857 | static, | |
| 858 | dynamic, | |
| 859 | default, | |
| 860 | ||
| 861 | pub fn init(link_mode: ?std.builtin.LinkMode) Linkage { | |
| 862 | return switch (link_mode orelse return .default) { | |
| 863 | .static => .static, | |
| 864 | .dynamic => .dynamic, | |
| 865 | }; | |
| 866 | } | |
| 867 | ||
| 868 | pub fn unwrap(this: @This()) ?std.builtin.LinkMode { | |
| 869 | return switch (this) { | |
| 870 | .static => .static, | |
| 871 | .dynamic => .dynamic, | |
| 872 | .default => null, | |
| 873 | }; | |
| 874 | } | |
| 875 | }; | |
| 876 | pub const Kind = enum(u3) { | |
| 877 | exe, | |
| 878 | lib, | |
| 879 | obj, | |
| 880 | @"test", | |
| 881 | test_obj, | |
| 882 | ||
| 883 | pub fn isTest(kind: Kind) bool { | |
| 884 | return switch (kind) { | |
| 885 | .exe, .lib, .obj => false, | |
| 886 | .@"test", .test_obj => true, | |
| 887 | }; | |
| 888 | } | |
| 889 | ||
| 890 | pub fn toOutputMode(kind: Kind) std.builtin.OutputMode { | |
| 891 | return switch (kind) { | |
| 892 | .exe, .@"test" => .Exe, | |
| 893 | .lib => .Lib, | |
| 894 | .obj, .test_obj => .Obj, | |
| 895 | }; | |
| 896 | } | |
| 897 | }; | |
| 898 | pub const Subsystem = enum(u4) { | |
| 899 | console, | |
| 900 | windows, | |
| 901 | posix, | |
| 902 | native, | |
| 903 | efi_application, | |
| 904 | efi_boot_service_driver, | |
| 905 | efi_rom, | |
| 906 | efi_runtime_driver, | |
| 907 | default, | |
| 908 | ||
| 909 | pub fn init(subsystem: ?std.zig.Subsystem) Subsystem { | |
| 910 | return switch (subsystem orelse return .default) { | |
| 911 | .console => .console, | |
| 912 | .windows => .windows, | |
| 913 | .posix => .posix, | |
| 914 | .native => .native, | |
| 915 | .efi_application => .efi_application, | |
| 916 | .efi_boot_service_driver => .efi_boot_service_driver, | |
| 917 | .efi_rom => .efi_rom, | |
| 918 | .efi_runtime_driver => .efi_runtime_driver, | |
| 919 | }; | |
| 920 | } | |
| 921 | }; | |
| 922 | ||
| 923 | pub const Flags = packed struct(u32) { | |
| 924 | tag: Tag = .compile, | |
| 925 | ||
| 926 | filters_len: bool, | |
| 927 | exec_cmd_args_len: bool, | |
| 928 | installed_headers_len: bool, | |
| 929 | force_undefined_symbols_len: bool, | |
| 930 | ||
| 931 | verbose_link: bool, | |
| 932 | verbose_cc: bool, | |
| 933 | rdynamic: bool, | |
| 934 | import_memory: bool, | |
| 935 | export_memory: bool, | |
| 936 | import_symbols: bool, | |
| 937 | import_table: bool, | |
| 938 | export_table: bool, | |
| 939 | shared_memory: bool, | |
| 940 | link_eh_frame_hdr: bool, | |
| 941 | link_emit_relocs: bool, | |
| 942 | link_function_sections: bool, | |
| 943 | link_data_sections: bool, | |
| 944 | linker_dynamicbase: bool, | |
| 945 | link_z_notext: bool, | |
| 946 | link_z_relro: bool, | |
| 947 | link_z_lazy: bool, | |
| 948 | link_z_defs: bool, | |
| 949 | headerpad_max_install_names: bool, | |
| 950 | dead_strip_dylibs: bool, | |
| 951 | force_load_objc: bool, | |
| 952 | discard_local_symbols: bool, | |
| 953 | mingw_unicode_entry_point: bool, | |
| 954 | }; | |
| 955 | ||
| 956 | pub const Flags2 = packed struct(u32) { | |
| 957 | pie: DefaultingBool, | |
| 958 | formatted_panics: DefaultingBool, | |
| 959 | bundle_compiler_rt: DefaultingBool, | |
| 960 | bundle_ubsan_rt: DefaultingBool, | |
| 961 | each_lib_rpath: DefaultingBool, | |
| 962 | link_gc_sections: DefaultingBool, | |
| 963 | linker_allow_shlib_undefined: DefaultingBool, | |
| 964 | linker_allow_undefined_version: DefaultingBool, | |
| 965 | linker_enable_new_dtags: DefaultingBool, | |
| 966 | dll_export_fns: DefaultingBool, | |
| 967 | use_llvm: DefaultingBool, | |
| 968 | use_lld: DefaultingBool, | |
| 969 | use_new_linker: DefaultingBool, | |
| 970 | allow_so_scripts: DefaultingBool, | |
| 971 | sanitize_coverage_trace_pc_guard: DefaultingBool, | |
| 972 | linkage: Linkage, | |
| 973 | }; | |
| 974 | ||
| 975 | pub const Flags3 = packed struct(u32) { | |
| 976 | is_linking_libc: bool, | |
| 977 | is_linking_libcpp: bool, | |
| 978 | version: bool, | |
| 979 | initial_memory: bool, | |
| 980 | max_memory: bool, | |
| 981 | kind: Kind, | |
| 982 | compress_debug_sections: std.zig.CompressDebugSections, | |
| 983 | global_base: bool, | |
| 984 | test_runner: TestRunner.Tag, | |
| 985 | wasi_exec_model: WasiExecModel, | |
| 986 | win32_manifest: bool, | |
| 987 | win32_module_definition: bool, | |
| 988 | zig_lib_dir: bool, | |
| 989 | rc_includes: std.zig.RcIncludes, | |
| 990 | image_base: bool, | |
| 991 | build_id: BuildId, | |
| 992 | entry: Entry, | |
| 993 | lto: Lto, | |
| 994 | subsystem: Subsystem, | |
| 995 | }; | |
| 996 | ||
| 997 | pub const Flags4 = packed struct(u32) { | |
| 998 | libc_file: bool, | |
| 999 | link_z_common_page_size: bool, | |
| 1000 | link_z_max_page_size: bool, | |
| 1001 | pagezero_size: bool, | |
| 1002 | stack_size: bool, | |
| 1003 | headerpad_size: bool, | |
| 1004 | error_limit: bool, | |
| 1005 | install_name: bool, | |
| 1006 | entitlements: bool, | |
| 1007 | expect_errors: ExpectErrors.Tag, | |
| 1008 | linker_script: bool, | |
| 1009 | version_script: bool, | |
| 1010 | emit_directory: bool, | |
| 1011 | generated_docs: bool, | |
| 1012 | generated_asm: bool, | |
| 1013 | generated_bin: bool, | |
| 1014 | generated_pdb: bool, | |
| 1015 | generated_implib: bool, | |
| 1016 | generated_llvm_bc: bool, | |
| 1017 | generated_llvm_ir: bool, | |
| 1018 | generated_h: bool, | |
| 1019 | _: u9 = 0, | |
| 1020 | }; | |
| 1021 | ||
| 1022 | pub fn isDynamicLibrary(compile: *const Compile) bool { | |
| 1023 | return compile.flags3.kind == .lib and compile.flags2.linkage == .dynamic; | |
| 1024 | } | |
| 1025 | ||
| 1026 | pub fn isStaticLibrary(compile: *const Compile) bool { | |
| 1027 | return compile.flags3.kind == .lib and compile.flags2.linkage != .dynamic; | |
| 1028 | } | |
| 1029 | ||
| 1030 | pub fn producesImplib(compile: *const Compile, c: *const Configuration) bool { | |
| 1031 | return isDll(compile, c); | |
| 1032 | } | |
| 1033 | ||
| 1034 | pub fn isDll(compile: *const Compile, c: *const Configuration) bool { | |
| 1035 | return isDynamicLibrary(compile) and rootModuleTarget(compile, c).flags.os_tag == .windows; | |
| 1036 | } | |
| 1037 | ||
| 1038 | pub fn rootModuleTarget(compile: *const Compile, c: *const Configuration) TargetQuery { | |
| 1039 | return compile.root_module.get(c).resolved_target.get(c).?.result.get(c); | |
| 1040 | } | |
| 1041 | }; | |
| 1042 | ||
| 1043 | pub const CheckFile = struct { | |
| 1044 | flags: @This().Flags, | |
| 1045 | file: LazyPath.Index, | |
| 1046 | expected_exact: Storage.FlagOptional(.flags, .expected_exact, Bytes), | |
| 1047 | expected_matches: Storage.FlagLengthPrefixedList(.flags, .expected_matches, Bytes), | |
| 1048 | max_bytes: Storage.FlagOptional(.flags, .max_bytes, u32), | |
| 1049 | ||
| 1050 | pub const Flags = packed struct(u32) { | |
| 1051 | tag: Tag = .check_file, | |
| 1052 | expected_exact: bool, | |
| 1053 | expected_matches: bool, | |
| 1054 | max_bytes: bool, | |
| 1055 | _: u24 = 0, | |
| 1056 | }; | |
| 1057 | }; | |
| 1058 | ||
| 1059 | pub const ConfigHeader = struct { | |
| 1060 | flags: @This().Flags, | |
| 1061 | template_file: Storage.FlagOptional(.flags, .template_file, LazyPath.Index), | |
| 1062 | generated_dir: GeneratedFileIndex, | |
| 1063 | input_size_limit: Storage.FlagOptional(.flags, .input_size_limit, u64), | |
| 1064 | include_path: String, | |
| 1065 | include_guard: Storage.FlagOptional(.flags, .include_guard, String), | |
| 1066 | values: Storage.LengthPrefixedList(Value.Pair), | |
| 1067 | ||
| 1068 | pub const Style = enum(u3) { | |
| 1069 | autoconf_undef, | |
| 1070 | autoconf_at, | |
| 1071 | cmake, | |
| 1072 | blank, | |
| 1073 | nasm, | |
| 1074 | ||
| 1075 | pub fn init(s: std.Build.Step.ConfigHeader.Style) Style { | |
| 1076 | return switch (s) { | |
| 1077 | .autoconf_undef => .autoconf_undef, | |
| 1078 | .autoconf_at => .autoconf_at, | |
| 1079 | .cmake => .cmake, | |
| 1080 | .blank => .blank, | |
| 1081 | .nasm => .nasm, | |
| 1082 | }; | |
| 1083 | } | |
| 1084 | }; | |
| 1085 | ||
| 1086 | pub const Value = struct { | |
| 1087 | flags: @This().Flags, | |
| 1088 | i64: Storage.EnumOptional(.flags, .tag, .i64, i64), | |
| 1089 | u64: Storage.EnumOptional(.flags, .tag, .u64, u64), | |
| 1090 | ident: Storage.EnumOptional(.flags, .tag, .ident, String), | |
| 1091 | string: Storage.EnumOptional(.flags, .tag, .string, String), | |
| 1092 | ||
| 1093 | pub const Flags = packed struct(u32) { | |
| 1094 | tag: Value.Tag, | |
| 1095 | small: u29, | |
| 1096 | }; | |
| 1097 | ||
| 1098 | pub const Tag = enum(u3) { | |
| 1099 | ident, | |
| 1100 | string, | |
| 1101 | small_unsigned, | |
| 1102 | small_signed, | |
| 1103 | i64, | |
| 1104 | u64, | |
| 1105 | }; | |
| 1106 | ||
| 1107 | pub const Pair = extern struct { | |
| 1108 | key: String, | |
| 1109 | index: Value.Index, | |
| 1110 | }; | |
| 1111 | ||
| 1112 | pub const Index = enum(u32) { | |
| 1113 | int_0 = max_u32 - 5, | |
| 1114 | int_1 = max_u32 - 4, | |
| 1115 | bool_false = max_u32 - 3, | |
| 1116 | bool_true = max_u32 - 2, | |
| 1117 | undef = max_u32 - 1, | |
| 1118 | defined = max_u32, | |
| 1119 | _, | |
| 1120 | ||
| 1121 | pub fn unpack(this: @This(), c: *const Configuration) Unpacked { | |
| 1122 | return switch (this) { | |
| 1123 | .int_0 => .{ .u64 = 0 }, | |
| 1124 | .int_1 => .{ .u64 = 1 }, | |
| 1125 | .bool_false => .{ .bool = false }, | |
| 1126 | .bool_true => .{ .bool = true }, | |
| 1127 | .undef => .undef, | |
| 1128 | .defined => .defined, | |
| 1129 | _ => { | |
| 1130 | const value = extraData(c, Value, @intFromEnum(this)); | |
| 1131 | return switch (value.flags.tag) { | |
| 1132 | .ident => .{ .ident = value.ident.value.?.slice(c) }, | |
| 1133 | .string => .{ .string = value.string.value.?.slice(c) }, | |
| 1134 | .small_unsigned => .{ .u64 = value.flags.small }, | |
| 1135 | .small_signed => .{ .i64 = @as(i29, @bitCast(value.flags.small)) }, | |
| 1136 | .i64 => .{ .i64 = value.i64.value.? }, | |
| 1137 | .u64 => .{ .u64 = value.u64.value.? }, | |
| 1138 | }; | |
| 1139 | }, | |
| 1140 | }; | |
| 1141 | } | |
| 1142 | }; | |
| 1143 | ||
| 1144 | pub const Unpacked = union(enum) { | |
| 1145 | bool: bool, | |
| 1146 | undef, | |
| 1147 | defined, | |
| 1148 | i64: i64, | |
| 1149 | u64: u64, | |
| 1150 | ident: []const u8, | |
| 1151 | string: []const u8, | |
| 1152 | }; | |
| 1153 | ||
| 1154 | pub fn initSigned(x: i64) @This() { | |
| 1155 | return switch (x) { | |
| 1156 | 0 => unreachable, // should have been an Index | |
| 1157 | 1 => unreachable, // should have been an Index | |
| 1158 | 2...std.math.maxInt(u29) => .{ | |
| 1159 | .flags = .{ | |
| 1160 | .tag = .small_unsigned, | |
| 1161 | .small = @intCast(x), | |
| 1162 | }, | |
| 1163 | .i64 = .{ .value = null }, | |
| 1164 | .u64 = .{ .value = null }, | |
| 1165 | .ident = .{ .value = null }, | |
| 1166 | .string = .{ .value = null }, | |
| 1167 | }, | |
| 1168 | std.math.minInt(i29)...-1 => .{ | |
| 1169 | .flags = .{ | |
| 1170 | .tag = .small_signed, | |
| 1171 | .small = @bitCast(@as(i29, @intCast(x))), | |
| 1172 | }, | |
| 1173 | .i64 = .{ .value = null }, | |
| 1174 | .u64 = .{ .value = null }, | |
| 1175 | .ident = .{ .value = null }, | |
| 1176 | .string = .{ .value = null }, | |
| 1177 | }, | |
| 1178 | else => .{ | |
| 1179 | .flags = .{ | |
| 1180 | .tag = .i64, | |
| 1181 | .small = 0, | |
| 1182 | }, | |
| 1183 | .i64 = .{ .value = x }, | |
| 1184 | .u64 = .{ .value = null }, | |
| 1185 | .ident = .{ .value = null }, | |
| 1186 | .string = .{ .value = null }, | |
| 1187 | }, | |
| 1188 | }; | |
| 1189 | } | |
| 1190 | }; | |
| 1191 | ||
| 1192 | pub const Flags = packed struct(u32) { | |
| 1193 | tag: Tag = .config_header, | |
| 1194 | template_file: bool, | |
| 1195 | style: Style, | |
| 1196 | input_size_limit: bool, | |
| 1197 | include_guard: bool, | |
| 1198 | _: u21 = 0, | |
| 1199 | }; | |
| 1200 | }; | |
| 1201 | ||
| 1202 | pub const Fail = struct { | |
| 1203 | flags: @This().Flags = .{}, | |
| 1204 | msg: String, | |
| 1205 | ||
| 1206 | pub const Flags = packed struct(u32) { | |
| 1207 | tag: Tag = .fail, | |
| 1208 | _: u27 = 0, | |
| 1209 | }; | |
| 1210 | }; | |
| 1211 | ||
| 1212 | pub const Fmt = struct { | |
| 1213 | flags: @This().Flags, | |
| 1214 | paths: Storage.FlagLengthPrefixedList(.flags, .paths, LazyPath.Index), | |
| 1215 | exclude_paths: Storage.FlagLengthPrefixedList(.flags, .exclude_paths, LazyPath.Index), | |
| 1216 | ||
| 1217 | pub const Flags = packed struct(u32) { | |
| 1218 | tag: Tag = .fmt, | |
| 1219 | paths: bool, | |
| 1220 | exclude_paths: bool, | |
| 1221 | check: bool, | |
| 1222 | _: u24 = 0, | |
| 1223 | }; | |
| 1224 | }; | |
| 1225 | ||
| 1226 | pub const FindProgram = struct { | |
| 1227 | flags: @This().Flags = .{}, | |
| 1228 | names: StringList, | |
| 1229 | found_path: GeneratedFileIndex, | |
| 1230 | ||
| 1231 | pub const Flags = packed struct(u32) { | |
| 1232 | tag: Tag = .find_program, | |
| 1233 | _: u27 = 0, | |
| 1234 | }; | |
| 1235 | }; | |
| 1236 | ||
| 1237 | pub const InstallDir = struct { | |
| 1238 | flags: @This().Flags, | |
| 1239 | source_dir: LazyPath.Index, | |
| 1240 | dest_dir: InstallDestDir, | |
| 1241 | dest_sub_path: Storage.FlagOptional(.flags, .dest_sub_path, String), | |
| 1242 | exclude_extensions: Storage.FlagLengthPrefixedList(.flags, .exclude_extensions, String), | |
| 1243 | include_extensions: Storage.FlagLengthPrefixedList(.flags, .include_extensions, String), | |
| 1244 | blank_extensions: Storage.FlagLengthPrefixedList(.flags, .blank_extensions, String), | |
| 1245 | ||
| 1246 | pub const Flags = packed struct(u32) { | |
| 1247 | tag: Tag = .install_dir, | |
| 1248 | dest_sub_path: bool, | |
| 1249 | exclude_extensions: bool, | |
| 1250 | include_extensions: bool, | |
| 1251 | include_extensions_active: bool, | |
| 1252 | blank_extensions: bool, | |
| 1253 | _: u22 = 0, | |
| 1254 | }; | |
| 1255 | }; | |
| 1256 | ||
| 1257 | pub const InstallFile = struct { | |
| 1258 | flags: @This().Flags = .{}, | |
| 1259 | source: LazyPath.Index, | |
| 1260 | dest_dir: InstallDestDir, | |
| 1261 | dest_sub_path: String, | |
| 1262 | ||
| 1263 | pub const Flags = packed struct(u32) { | |
| 1264 | tag: Tag = .install_file, | |
| 1265 | _: u27 = 0, | |
| 1266 | }; | |
| 1267 | }; | |
| 1268 | ||
| 1269 | pub const ObjCopy = struct { | |
| 1270 | flags: @This().Flags, | |
| 1271 | input_file: LazyPath.Index, | |
| 1272 | output_file: GeneratedFileIndex, | |
| 1273 | basename: Storage.FlagOptional(.flags, .basename, String), | |
| 1274 | debug_file: Storage.FlagOptional(.flags, .debug_file, GeneratedFileIndex), | |
| 1275 | debug_basename: Storage.FlagOptional(.flags, .debug_basename, String), | |
| 1276 | only_section: Storage.FlagOptional(.flags, .only_section, String), | |
| 1277 | pad_to: Storage.FlagOptional(.flags, .pad_to, u64), | |
| 1278 | add_section: Storage.FlagLengthPrefixedList(.flags, .add_section, AddSection), | |
| 1279 | update_section: Storage.FlagLengthPrefixedList(.flags, .update_section, UpdateSection), | |
| 1280 | ||
| 1281 | pub const Format = enum(u2) { | |
| 1282 | binary, | |
| 1283 | hex, | |
| 1284 | elf, | |
| 1285 | default, | |
| 1286 | ||
| 1287 | pub fn init(f: ?std.Build.Step.ObjCopy.Format) @This() { | |
| 1288 | return switch (f orelse return .default) { | |
| 1289 | .binary => .binary, | |
| 1290 | .hex => .hex, | |
| 1291 | .elf => .elf, | |
| 1292 | }; | |
| 1293 | } | |
| 1294 | }; | |
| 1295 | ||
| 1296 | pub const Strip = enum(u2) { | |
| 1297 | none, | |
| 1298 | debug, | |
| 1299 | debug_and_symbols, | |
| 1300 | }; | |
| 1301 | ||
| 1302 | pub const AddSection = extern struct { | |
| 1303 | section_name: String, | |
| 1304 | file_path: LazyPath.Index, | |
| 1305 | }; | |
| 1306 | ||
| 1307 | pub const UpdateSection = extern struct { | |
| 1308 | section_name: String, | |
| 1309 | flags: @This().Flags, | |
| 1310 | ||
| 1311 | pub const Flags = packed struct(u32) { | |
| 1312 | section_flags: SectionFlags, | |
| 1313 | alignment: Alignment, | |
| 1314 | _: u17 = 0, | |
| 1315 | }; | |
| 1316 | }; | |
| 1317 | ||
| 1318 | pub const SectionFlags = packed struct(u9) { | |
| 1319 | /// add SHF_ALLOC | |
| 1320 | alloc: bool = false, | |
| 1321 | /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing | |
| 1322 | contents: bool = false, | |
| 1323 | /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents) | |
| 1324 | load: bool = false, | |
| 1325 | /// readonly: clear default SHF_WRITE flag | |
| 1326 | readonly: bool = false, | |
| 1327 | /// add SHF_EXECINSTR | |
| 1328 | code: bool = false, | |
| 1329 | /// add SHF_EXCLUDE | |
| 1330 | exclude: bool = false, | |
| 1331 | /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64 | |
| 1332 | large: bool = false, | |
| 1333 | /// add SHF_MERGE | |
| 1334 | merge: bool = false, | |
| 1335 | /// add SHF_STRINGS | |
| 1336 | strings: bool = false, | |
| 1337 | ||
| 1338 | pub const default: @This() = .{}; | |
| 1339 | }; | |
| 1340 | ||
| 1341 | pub const Flags = packed struct(u32) { | |
| 1342 | tag: Tag = .obj_copy, | |
| 1343 | basename: bool, | |
| 1344 | debug_file: bool, | |
| 1345 | debug_basename: bool, | |
| 1346 | format: Format, | |
| 1347 | strip: Strip, | |
| 1348 | compress_debug: bool, | |
| 1349 | only_section: bool, | |
| 1350 | pad_to: bool, | |
| 1351 | add_section: bool, | |
| 1352 | update_section: bool, | |
| 1353 | _: u15 = 0, | |
| 1354 | }; | |
| 1355 | }; | |
| 1356 | ||
| 1357 | pub const Options = struct { | |
| 1358 | flags: @This().Flags, | |
| 1359 | generated_file: GeneratedFileIndex, | |
| 1360 | contents: Bytes, | |
| 1361 | args: Storage.FlagLengthPrefixedList(.flags, .args, Arg), | |
| 1362 | ||
| 1363 | pub const Arg = extern struct { | |
| 1364 | name: String, | |
| 1365 | path: LazyPath.Index, | |
| 1366 | }; | |
| 1367 | ||
| 1368 | pub const Flags = packed struct(u32) { | |
| 1369 | tag: Tag = .options, | |
| 1370 | args: bool, | |
| 1371 | _: u26 = 0, | |
| 1372 | }; | |
| 1373 | }; | |
| 1374 | ||
| 1375 | pub const TranslateC = struct { | |
| 1376 | flags: @This().Flags, | |
| 1377 | src_path: LazyPath.Index, | |
| 1378 | output_file: GeneratedFileIndex, | |
| 1379 | include_dirs: Storage.UnionList(.flags, .include_dirs, Module.IncludeDir), | |
| 1380 | system_libs: Storage.FlagLengthPrefixedList(.flags, .system_libs, SystemLib.Index), | |
| 1381 | c_macros: Storage.FlagLengthPrefixedList(.flags, .c_macros, String), | |
| 1382 | target: ResolvedTarget.OptionalIndex, | |
| 1383 | ||
| 1384 | pub const Flags = packed struct(u32) { | |
| 1385 | tag: Tag = .translate_c, | |
| 1386 | include_dirs: bool, | |
| 1387 | system_libs: bool, | |
| 1388 | c_macros: bool, | |
| 1389 | link_libc: bool, | |
| 1390 | optimize: Module.Optimize, | |
| 1391 | _: u20 = 0, | |
| 1392 | }; | |
| 1393 | }; | |
| 1394 | ||
| 1395 | pub const UpdateSourceFiles = struct { | |
| 1396 | flags: @This().Flags, | |
| 1397 | embeds: Storage.FlagLengthPrefixedList(.flags, .embeds, Embed), | |
| 1398 | copies: Storage.FlagLengthPrefixedList(.flags, .copies, Copy), | |
| 1399 | ||
| 1400 | pub const Embed = WriteFile.Embed; | |
| 1401 | pub const Copy = WriteFile.Copy; | |
| 1402 | ||
| 1403 | pub const Flags = packed struct(u32) { | |
| 1404 | tag: Tag = .update_source_files, | |
| 1405 | embeds: bool, | |
| 1406 | copies: bool, | |
| 1407 | _: u25 = 0, | |
| 1408 | }; | |
| 1409 | }; | |
| 1410 | ||
| 1411 | pub const WriteFile = struct { | |
| 1412 | flags: @This().Flags, | |
| 1413 | generated_directory: GeneratedFileIndex, | |
| 1414 | embeds: Storage.FlagLengthPrefixedList(.flags, .embeds, Embed), | |
| 1415 | copies: Storage.FlagLengthPrefixedList(.flags, .copies, Copy), | |
| 1416 | directories: Storage.FlagLengthPrefixedList(.flags, .directories, Directory), | |
| 1417 | mutate_path: Storage.EnumOptional(.flags, .mode, .mutate, LazyPath.Index), | |
| 1418 | ||
| 1419 | pub const Embed = extern struct { | |
| 1420 | sub_path: String, | |
| 1421 | contents: Bytes, | |
| 1422 | }; | |
| 1423 | ||
| 1424 | pub const Copy = extern struct { | |
| 1425 | sub_path: String, | |
| 1426 | src_file: LazyPath.Index, | |
| 1427 | }; | |
| 1428 | ||
| 1429 | pub const Directory = extern struct { | |
| 1430 | sub_path: String, | |
| 1431 | src_path: LazyPath.Index, | |
| 1432 | exclude_extensions: OptionalStringList, | |
| 1433 | include_extensions: OptionalStringList, | |
| 1434 | }; | |
| 1435 | ||
| 1436 | pub const Mode = enum(u2) { | |
| 1437 | whole_cached, | |
| 1438 | tmp, | |
| 1439 | mutate, | |
| 1440 | }; | |
| 1441 | ||
| 1442 | pub const Flags = packed struct(u32) { | |
| 1443 | tag: Tag = .write_file, | |
| 1444 | embeds: bool, | |
| 1445 | copies: bool, | |
| 1446 | directories: bool, | |
| 1447 | mode: Mode, | |
| 1448 | _: u22 = 0, | |
| 1449 | }; | |
| 1450 | }; | |
| 1451 | ||
| 1452 | pub fn flags(s: *const Step, c: *const Configuration) Flags { | |
| 1453 | return @bitCast(c.extra[@intFromEnum(s.extended)]); | |
| 1454 | } | |
| 1455 | }; | |
| 1456 | ||
| 1457 | pub const MaxRss = enum(u32) { | |
| 1458 | none = 0, | |
| 1459 | _, | |
| 1460 | ||
| 1461 | pub fn toBytes(mr: MaxRss) usize { | |
| 1462 | const x: usize = @intFromEnum(mr); | |
| 1463 | return x << 8; | |
| 1464 | } | |
| 1465 | ||
| 1466 | pub fn fromBytes(bytes: usize) MaxRss { | |
| 1467 | return @enumFromInt(bytes >> 8); | |
| 1468 | } | |
| 1469 | }; | |
| 1470 | ||
| 1471 | pub const LazyPath = union(@This().Tag) { | |
| 1472 | source_path: SourcePath, | |
| 1473 | relative: Relative, | |
| 1474 | generated: Generated, | |
| 1475 | ||
| 1476 | pub const Tag = enum(u8) { | |
| 1477 | /// A source file path relative to build root. | |
| 1478 | source_path, | |
| 1479 | /// Relative to the directory indicated in flags. | |
| 1480 | relative, | |
| 1481 | /// Path is available only after it is populated by its owning step. | |
| 1482 | generated, | |
| 1483 | }; | |
| 1484 | ||
| 1485 | pub const Flags = packed struct(u32) { | |
| 1486 | tag: Tag, | |
| 1487 | _: u24 = 0, | |
| 1488 | }; | |
| 1489 | ||
| 1490 | /// An index into `extra`. | |
| 1491 | pub const Index = enum(u32) { | |
| 1492 | _, | |
| 1493 | ||
| 1494 | pub fn get(this: @This(), c: *const Configuration) LazyPath { | |
| 1495 | return extraData(c, LazyPath, @intFromEnum(this)); | |
| 1496 | } | |
| 1497 | }; | |
| 1498 | ||
| 1499 | /// An index into `extra`, or `null`. | |
| 1500 | pub const OptionalIndex = enum(u32) { | |
| 1501 | none = max_u32, | |
| 1502 | _, | |
| 1503 | ||
| 1504 | pub fn unwrap(this: @This()) ?Index { | |
| 1505 | return switch (this) { | |
| 1506 | .none => null, | |
| 1507 | else => @enumFromInt(@intFromEnum(this)), | |
| 1508 | }; | |
| 1509 | } | |
| 1510 | }; | |
| 1511 | ||
| 1512 | pub const SourcePath = struct { | |
| 1513 | flags: @This().Flags = .{}, | |
| 1514 | owner: Package.Index, | |
| 1515 | sub_path: String, | |
| 1516 | ||
| 1517 | pub const Flags = packed struct(u32) { | |
| 1518 | tag: Tag = .source_path, | |
| 1519 | _: u24 = 0, | |
| 1520 | }; | |
| 1521 | }; | |
| 1522 | ||
| 1523 | pub const Generated = struct { | |
| 1524 | flags: @This().Flags = .{}, | |
| 1525 | index: GeneratedFileIndex, | |
| 1526 | /// Applied after `up`. | |
| 1527 | sub_path: String = .empty, | |
| 1528 | ||
| 1529 | pub const Flags = packed struct(u32) { | |
| 1530 | tag: Tag = .generated, | |
| 1531 | /// The number of parent directories to go up. | |
| 1532 | /// 0 means the generated file itself. | |
| 1533 | /// 1 means the directory of the generated file. | |
| 1534 | /// 2 means the parent of that directory, and so on. | |
| 1535 | up: u24 = 0, | |
| 1536 | }; | |
| 1537 | }; | |
| 1538 | ||
| 1539 | pub const Relative = struct { | |
| 1540 | flags: @This().Flags, | |
| 1541 | sub_path: String, | |
| 1542 | ||
| 1543 | pub const Flags = packed struct(u32) { | |
| 1544 | tag: Tag = .relative, | |
| 1545 | base: Path.Base, | |
| 1546 | _: u16 = 0, | |
| 1547 | }; | |
| 1548 | }; | |
| 1549 | }; | |
| 1550 | ||
| 1551 | pub const GeneratedFileIndex = enum(u32) { | |
| 1552 | _, | |
| 1553 | }; | |
| 1554 | ||
| 1555 | pub const OptionalGeneratedFileIndex = enum(u32) { | |
| 1556 | none = max_u32, | |
| 1557 | _, | |
| 1558 | ||
| 1559 | pub fn init(i: ?GeneratedFileIndex) OptionalGeneratedFileIndex { | |
| 1560 | return @enumFromInt(@intFromEnum(i orelse return .none)); | |
| 1561 | } | |
| 1562 | ||
| 1563 | pub fn unwrap(this: @This()) ?GeneratedFileIndex { | |
| 1564 | return switch (this) { | |
| 1565 | .none => null, | |
| 1566 | else => @enumFromInt(@intFromEnum(this)), | |
| 1567 | }; | |
| 1568 | } | |
| 1569 | }; | |
| 1570 | ||
| 1571 | pub const Package = struct { | |
| 1572 | dep_prefix: String, | |
| 1573 | hash: String, | |
| 1574 | root_path: String, | |
| 1575 | ||
| 1576 | pub const Index = enum(u32) { | |
| 1577 | root = max_u32, | |
| 1578 | _, | |
| 1579 | ||
| 1580 | /// Returns `null` for root package. | |
| 1581 | pub fn get(i: @This(), c: *const Configuration) ?Package { | |
| 1582 | if (i == .root) return null; | |
| 1583 | return extraData(c, Package, @intFromEnum(i)); | |
| 1584 | } | |
| 1585 | ||
| 1586 | pub fn depPrefixSlice(i: @This(), c: *const Configuration) [:0]const u8 { | |
| 1587 | const package = get(i, c) orelse return ""; | |
| 1588 | return package.dep_prefix.slice(c); | |
| 1589 | } | |
| 1590 | }; | |
| 1591 | }; | |
| 1592 | ||
| 1593 | pub const Module = struct { | |
| 1594 | flags: Flags, | |
| 1595 | flags2: Flags2, | |
| 1596 | import_table: ImportTable.Index, | |
| 1597 | owner: Package.Index, | |
| 1598 | root_source_file: LazyPath.OptionalIndex, | |
| 1599 | resolved_target: ResolvedTarget.OptionalIndex, | |
| 1600 | c_macros: Storage.FlagLengthPrefixedList(.flags, .c_macros, String), | |
| 1601 | lib_paths: Storage.FlagLengthPrefixedList(.flags, .lib_paths, LazyPath.Index), | |
| 1602 | export_symbol_names: Storage.FlagLengthPrefixedList(.flags, .export_symbol_names, String), | |
| 1603 | include_dirs: Storage.UnionList(.flags, .include_dirs, IncludeDir), | |
| 1604 | rpaths: Storage.UnionList(.flags, .rpaths, RPath), | |
| 1605 | link_objects: Storage.UnionList(.flags, .link_objects, LinkObject), | |
| 1606 | frameworks: Storage.FlagLengthPrefixedList(.flags, .frameworks, Framework), | |
| 1607 | ||
| 1608 | pub const Optimize = enum(u3) { | |
| 1609 | debug, | |
| 1610 | safe, | |
| 1611 | fast, | |
| 1612 | small, | |
| 1613 | default, | |
| 1614 | ||
| 1615 | pub fn init(o: ?std.builtin.OptimizeMode) Optimize { | |
| 1616 | return switch (o orelse return .default) { | |
| 1617 | .Debug => .debug, | |
| 1618 | .ReleaseSafe => .safe, | |
| 1619 | .ReleaseFast => .fast, | |
| 1620 | .ReleaseSmall => .small, | |
| 1621 | }; | |
| 1622 | } | |
| 1623 | }; | |
| 1624 | ||
| 1625 | pub const UnwindTables = enum(u2) { | |
| 1626 | none, | |
| 1627 | sync, | |
| 1628 | async, | |
| 1629 | default, | |
| 1630 | ||
| 1631 | pub fn init(ut: ?std.builtin.UnwindTables) UnwindTables { | |
| 1632 | return switch (ut orelse return .default) { | |
| 1633 | .none => .none, | |
| 1634 | .sync => .sync, | |
| 1635 | .async => .async, | |
| 1636 | }; | |
| 1637 | } | |
| 1638 | }; | |
| 1639 | ||
| 1640 | pub const SanitizeC = enum(u2) { | |
| 1641 | off, | |
| 1642 | trap, | |
| 1643 | full, | |
| 1644 | default, | |
| 1645 | ||
| 1646 | pub fn init(sc: ?std.zig.SanitizeC) SanitizeC { | |
| 1647 | return switch (sc orelse return .default) { | |
| 1648 | .off => .off, | |
| 1649 | .trap => .trap, | |
| 1650 | .full => .full, | |
| 1651 | }; | |
| 1652 | } | |
| 1653 | }; | |
| 1654 | ||
| 1655 | pub const DwarfFormat = enum(u2) { | |
| 1656 | @"32", | |
| 1657 | @"64", | |
| 1658 | default, | |
| 1659 | ||
| 1660 | pub fn init(df: ?std.dwarf.Format) DwarfFormat { | |
| 1661 | return switch (df orelse return .default) { | |
| 1662 | .@"32" => .@"32", | |
| 1663 | .@"64" => .@"64", | |
| 1664 | }; | |
| 1665 | } | |
| 1666 | }; | |
| 1667 | ||
| 1668 | pub const Index = enum(u32) { | |
| 1669 | _, | |
| 1670 | ||
| 1671 | pub fn get(this: @This(), c: *const Configuration) Module { | |
| 1672 | return extraData(c, Module, @intFromEnum(this)); | |
| 1673 | } | |
| 1674 | }; | |
| 1675 | ||
| 1676 | pub const Flags = packed struct(u32) { | |
| 1677 | optimize: Optimize, | |
| 1678 | strip: DefaultingBool, | |
| 1679 | unwind_tables: UnwindTables, | |
| 1680 | dwarf_format: DwarfFormat, | |
| 1681 | single_threaded: DefaultingBool, | |
| 1682 | stack_protector: DefaultingBool, | |
| 1683 | stack_check: DefaultingBool, | |
| 1684 | sanitize_c: SanitizeC, | |
| 1685 | sanitize_thread: DefaultingBool, | |
| 1686 | fuzz: DefaultingBool, | |
| 1687 | code_model: std.builtin.CodeModel, | |
| 1688 | c_macros: bool, | |
| 1689 | include_dirs: bool, | |
| 1690 | lib_paths: bool, | |
| 1691 | rpaths: bool, | |
| 1692 | frameworks: bool, | |
| 1693 | link_objects: bool, | |
| 1694 | export_symbol_names: bool, | |
| 1695 | }; | |
| 1696 | ||
| 1697 | pub const Flags2 = packed struct(u32) { | |
| 1698 | valgrind: DefaultingBool, | |
| 1699 | pic: DefaultingBool, | |
| 1700 | red_zone: DefaultingBool, | |
| 1701 | omit_frame_pointer: DefaultingBool, | |
| 1702 | error_tracing: DefaultingBool, | |
| 1703 | link_libc: DefaultingBool, | |
| 1704 | link_libcpp: DefaultingBool, | |
| 1705 | no_builtin: DefaultingBool, | |
| 1706 | _: u16 = 0, | |
| 1707 | }; | |
| 1708 | ||
| 1709 | pub const IncludeDir = union(enum(u3)) { | |
| 1710 | path: LazyPath.Index, | |
| 1711 | path_system: LazyPath.Index, | |
| 1712 | path_after: LazyPath.Index, | |
| 1713 | framework_path: LazyPath.Index, | |
| 1714 | framework_path_system: LazyPath.Index, | |
| 1715 | /// Always `Step.Tag.config_header`. | |
| 1716 | config_header_step: Step.Index, | |
| 1717 | embed_path: LazyPath.Index, | |
| 1718 | }; | |
| 1719 | ||
| 1720 | pub const RPath = union(enum(u1)) { | |
| 1721 | lazy_path: LazyPath.Index, | |
| 1722 | special: String, | |
| 1723 | }; | |
| 1724 | ||
| 1725 | pub const LinkObject = union(enum(u3)) { | |
| 1726 | static_path: LazyPath.Index, | |
| 1727 | /// Always `Step.Tag.compile`. | |
| 1728 | other_step: Step.Index, | |
| 1729 | system_lib: SystemLib.Index, | |
| 1730 | assembly_file: LazyPath.Index, | |
| 1731 | c_source_file: CSourceFile.Index, | |
| 1732 | c_source_files: CSourceFiles.Index, | |
| 1733 | win32_resource_file: RcSourceFile.Index, | |
| 1734 | }; | |
| 1735 | ||
| 1736 | pub const Framework = extern struct { | |
| 1737 | flags: @This().Flags, | |
| 1738 | name: String, | |
| 1739 | ||
| 1740 | pub const Flags = packed struct(u32) { | |
| 1741 | needed: bool, | |
| 1742 | weak: bool, | |
| 1743 | _: u30 = 0, | |
| 1744 | }; | |
| 1745 | }; | |
| 1746 | }; | |
| 1747 | ||
| 1748 | pub const ImportTable = struct { | |
| 1749 | imports: Storage.MultiList(Import), | |
| 1750 | ||
| 1751 | pub const Import = struct { | |
| 1752 | name: String, | |
| 1753 | module: Module.Index, | |
| 1754 | }; | |
| 1755 | ||
| 1756 | /// Points into `extra`. | |
| 1757 | pub const Index = enum(u32) { | |
| 1758 | invalid = max_u32, | |
| 1759 | _, | |
| 1760 | ||
| 1761 | pub fn get(this: @This(), c: *const Configuration) ImportTable { | |
| 1762 | return switch (this) { | |
| 1763 | .invalid => unreachable, | |
| 1764 | _ => extraData(c, ImportTable, @intFromEnum(this)), | |
| 1765 | }; | |
| 1766 | } | |
| 1767 | }; | |
| 1768 | }; | |
| 1769 | ||
| 1770 | pub const Deps = struct { | |
| 1771 | steps: Storage.LengthPrefixedList(Step.Index), | |
| 1772 | ||
| 1773 | pub const Index = enum(u32) { | |
| 1774 | _, | |
| 1775 | ||
| 1776 | pub fn get(this: @This(), c: *const Configuration) Deps { | |
| 1777 | return extraData(c, Deps, @intFromEnum(this)); | |
| 1778 | } | |
| 1779 | ||
| 1780 | pub fn slice(this: @This(), c: *const Configuration) []const Step.Index { | |
| 1781 | return get(this, c).steps.slice; | |
| 1782 | } | |
| 1783 | }; | |
| 1784 | }; | |
| 1785 | ||
| 1786 | pub const EnvironMap = struct { | |
| 1787 | keys: StringList, | |
| 1788 | values: StringList, | |
| 1789 | ||
| 1790 | pub const Index = IndexType(@This()); | |
| 1791 | }; | |
| 1792 | ||
| 1793 | /// Points into `extra`, where the first element is count of strings, following | |
| 1794 | /// elements is `String` per count. | |
| 1795 | /// | |
| 1796 | /// Stored identically to `Deps`. | |
| 1797 | pub const StringList = enum(u32) { | |
| 1798 | _, | |
| 1799 | ||
| 1800 | pub fn slice(this: @This(), c: *const Configuration) []const String { | |
| 1801 | const len = c.extra[@intFromEnum(this)]; | |
| 1802 | return @ptrCast(c.extra[@intFromEnum(this) + 1 ..][0..len]); | |
| 1803 | } | |
| 1804 | }; | |
| 1805 | ||
| 1806 | pub const OptionalStringList = enum(u32) { | |
| 1807 | none = max_u32, | |
| 1808 | _, | |
| 1809 | ||
| 1810 | pub fn init(opt_string_list: ?StringList) OptionalStringList { | |
| 1811 | const sl = opt_string_list orelse return .none; | |
| 1812 | const result: OptionalStringList = @enumFromInt(@intFromEnum(sl)); | |
| 1813 | assert(result != .none); | |
| 1814 | return result; | |
| 1815 | } | |
| 1816 | ||
| 1817 | pub fn unwrap(this: @This()) ?StringList { | |
| 1818 | if (this == .none) return null; | |
| 1819 | return @enumFromInt(@intFromEnum(this)); | |
| 1820 | } | |
| 1821 | ||
| 1822 | pub fn slice(this: @This(), c: *const Configuration) ?[]const String { | |
| 1823 | return (unwrap(this) orelse return null).slice(c); | |
| 1824 | } | |
| 1825 | }; | |
| 1826 | ||
| 1827 | pub const Path = extern struct { | |
| 1828 | base: Base, | |
| 1829 | sub: String, | |
| 1830 | ||
| 1831 | pub const Base = enum(u8) { | |
| 1832 | cwd, | |
| 1833 | local_cache, | |
| 1834 | global_cache, | |
| 1835 | build_root, | |
| 1836 | zig_exe, | |
| 1837 | zig_lib, | |
| 1838 | install_prefix, | |
| 1839 | install_lib, | |
| 1840 | install_bin, | |
| 1841 | install_include, | |
| 1842 | }; | |
| 1843 | ||
| 1844 | pub fn toCachePath(path: Path, c: *const Configuration, arena: Allocator) std.Build.Cache.Path { | |
| 1845 | _ = c; | |
| 1846 | _ = arena; | |
| 1847 | _ = path; | |
| 1848 | @panic("TODO"); | |
| 1849 | } | |
| 1850 | }; | |
| 1851 | ||
| 1852 | pub const InstallDestDir = enum(u32) { | |
| 1853 | none = max_u32 - 4, | |
| 1854 | prefix = max_u32 - 3, | |
| 1855 | lib = max_u32 - 2, | |
| 1856 | bin = max_u32 - 1, | |
| 1857 | header = max_u32, | |
| 1858 | /// A `String` path relative to the prefix. | |
| 1859 | _, | |
| 1860 | ||
| 1861 | pub fn initCustom(sub_path: String) InstallDestDir { | |
| 1862 | assert(@intFromEnum(sub_path) < @intFromEnum(InstallDestDir.none)); | |
| 1863 | return @enumFromInt(@intFromEnum(sub_path)); | |
| 1864 | } | |
| 1865 | ||
| 1866 | pub const Unpacked = union(enum) { | |
| 1867 | prefix, | |
| 1868 | lib, | |
| 1869 | bin, | |
| 1870 | header, | |
| 1871 | sub_path: String, | |
| 1872 | }; | |
| 1873 | ||
| 1874 | pub fn unpack(this: @This()) ?Unpacked { | |
| 1875 | return switch (this) { | |
| 1876 | .none => null, | |
| 1877 | .prefix => .prefix, | |
| 1878 | .lib => .lib, | |
| 1879 | .bin => .bin, | |
| 1880 | .header => .header, | |
| 1881 | _ => .{ .sub_path = @enumFromInt(@intFromEnum(this)) }, | |
| 1882 | }; | |
| 1883 | } | |
| 1884 | }; | |
| 1885 | ||
| 1886 | /// Points into `string_bytes`, null-terminated. | |
| 1887 | pub const OptionalString = enum(u32) { | |
| 1888 | empty = 0, | |
| 1889 | /// The string "root". | |
| 1890 | root = 1, | |
| 1891 | none = max_u32, | |
| 1892 | _, | |
| 1893 | ||
| 1894 | pub fn init(s: String) OptionalString { | |
| 1895 | const result: OptionalString = @enumFromInt(@intFromEnum(s)); | |
| 1896 | assert(result != .none); | |
| 1897 | return result; | |
| 1898 | } | |
| 1899 | ||
| 1900 | pub fn unwrap(this: @This()) ?String { | |
| 1901 | if (this == .none) return null; | |
| 1902 | return @enumFromInt(@intFromEnum(this)); | |
| 1903 | } | |
| 1904 | ||
| 1905 | pub fn slice(this: @This(), c: *const Configuration) ?[:0]const u8 { | |
| 1906 | return (unwrap(this) orelse return null).slice(c); | |
| 1907 | } | |
| 1908 | }; | |
| 1909 | ||
| 1910 | /// Points into `string_bytes`, null-terminated. | |
| 1911 | pub const String = enum(u32) { | |
| 1912 | empty = 0, | |
| 1913 | /// The string "root". | |
| 1914 | root = 1, | |
| 1915 | _, | |
| 1916 | ||
| 1917 | pub fn slice(index: String, c: *const Configuration) [:0]const u8 { | |
| 1918 | const start_slice = c.string_bytes[@intFromEnum(index)..]; | |
| 1919 | return start_slice[0..std.mem.indexOfScalar(u8, start_slice, 0).? :0]; | |
| 1920 | } | |
| 1921 | }; | |
| 1922 | ||
| 1923 | /// Arbitrary sequence of bytes that may contain null bytes. | |
| 1924 | pub const Bytes = extern struct { | |
| 1925 | /// Points into `string_bytes`. | |
| 1926 | index: u32, | |
| 1927 | len: u32, | |
| 1928 | ||
| 1929 | pub fn slice(bytes: Bytes, c: *const Configuration) []const u8 { | |
| 1930 | return c.string_bytes[bytes.index..][0..bytes.len]; | |
| 1931 | } | |
| 1932 | }; | |
| 1933 | ||
| 1934 | /// Stored as a power-of-two, with one special value to indicate none. | |
| 1935 | pub const Alignment = enum(u6) { | |
| 1936 | @"1" = 0, | |
| 1937 | @"2" = 1, | |
| 1938 | @"4" = 2, | |
| 1939 | @"8" = 3, | |
| 1940 | @"16" = 4, | |
| 1941 | @"32" = 5, | |
| 1942 | @"64" = 6, | |
| 1943 | none = std.math.maxInt(u6), | |
| 1944 | _, | |
| 1945 | ||
| 1946 | pub fn init(optional_alignment: ?std.mem.Alignment) @This() { | |
| 1947 | const a = optional_alignment orelse return .none; | |
| 1948 | return @enumFromInt(@intFromEnum(a)); | |
| 1949 | } | |
| 1950 | ||
| 1951 | pub fn toBytes(a: @This()) ?u64 { | |
| 1952 | return switch (a) { | |
| 1953 | .none => null, | |
| 1954 | else => @as(u64, 1) << @intFromEnum(a), | |
| 1955 | }; | |
| 1956 | } | |
| 1957 | }; | |
| 1958 | ||
| 1959 | pub const DefaultingBool = enum(u2) { | |
| 1960 | false, | |
| 1961 | true, | |
| 1962 | default, | |
| 1963 | ||
| 1964 | pub fn init(b: ?bool) DefaultingBool { | |
| 1965 | return switch (b orelse return .default) { | |
| 1966 | false => .false, | |
| 1967 | true => .true, | |
| 1968 | }; | |
| 1969 | } | |
| 1970 | ||
| 1971 | pub fn toBool(db: DefaultingBool) ?bool { | |
| 1972 | return switch (db) { | |
| 1973 | .false => false, | |
| 1974 | .true => true, | |
| 1975 | .default => null, | |
| 1976 | }; | |
| 1977 | } | |
| 1978 | }; | |
| 1979 | ||
| 1980 | pub const SystemLib = struct { | |
| 1981 | name: String, | |
| 1982 | flags: Flags, | |
| 1983 | ||
| 1984 | pub const Index = enum(u32) { | |
| 1985 | _, | |
| 1986 | ||
| 1987 | pub fn get(this: @This(), c: *const Configuration) SystemLib { | |
| 1988 | return extraData(c, SystemLib, @intFromEnum(this)); | |
| 1989 | } | |
| 1990 | }; | |
| 1991 | ||
| 1992 | pub const UsePkgConfig = enum(u2) { | |
| 1993 | /// Don't use pkg-config, just pass -lfoo where foo is name. | |
| 1994 | no, | |
| 1995 | /// Try to get information on how to link the library from pkg-config. | |
| 1996 | /// If that fails, fall back to passing -lfoo where foo is name. | |
| 1997 | yes, | |
| 1998 | /// Try to get information on how to link the library from pkg-config. | |
| 1999 | /// If that fails, error out. | |
| 2000 | force, | |
| 2001 | }; | |
| 2002 | ||
| 2003 | pub const LinkMode = std.builtin.LinkMode; | |
| 2004 | ||
| 2005 | pub const Flags = packed struct(u32) { | |
| 2006 | needed: bool, | |
| 2007 | weak: bool, | |
| 2008 | use_pkg_config: UsePkgConfig, | |
| 2009 | preferred_link_mode: LinkMode, | |
| 2010 | search_strategy: SearchStrategy, | |
| 2011 | _: u25 = 0, | |
| 2012 | }; | |
| 2013 | ||
| 2014 | pub const SearchStrategy = enum(u2) { paths_first, mode_first, no_fallback }; | |
| 2015 | }; | |
| 2016 | ||
| 2017 | pub const CSourceFiles = struct { | |
| 2018 | flags: Flags, | |
| 2019 | root: LazyPath.Index, | |
| 2020 | args: Storage.FlagList(.flags, .args_len, String), | |
| 2021 | sub_paths: Storage.LengthPrefixedList(String), | |
| 2022 | ||
| 2023 | pub const Index = enum(u32) { | |
| 2024 | _, | |
| 2025 | ||
| 2026 | pub fn get(this: @This(), c: *const Configuration) CSourceFiles { | |
| 2027 | return extraData(c, CSourceFiles, @intFromEnum(this)); | |
| 2028 | } | |
| 2029 | }; | |
| 2030 | ||
| 2031 | pub const Flags = packed struct(u32) { | |
| 2032 | /// C compiler CLI flags. | |
| 2033 | args_len: u29, | |
| 2034 | lang: OptionalCSourceLanguage, | |
| 2035 | }; | |
| 2036 | }; | |
| 2037 | ||
| 2038 | pub const CSourceFile = struct { | |
| 2039 | flags: Flags, | |
| 2040 | file: LazyPath.Index, | |
| 2041 | args: Storage.FlagList(.flags, .args_len, String), | |
| 2042 | ||
| 2043 | pub const Index = enum(u32) { | |
| 2044 | _, | |
| 2045 | ||
| 2046 | pub fn get(this: @This(), c: *const Configuration) CSourceFile { | |
| 2047 | return extraData(c, CSourceFile, @intFromEnum(this)); | |
| 2048 | } | |
| 2049 | }; | |
| 2050 | ||
| 2051 | pub const Flags = packed struct(u32) { | |
| 2052 | /// C compiler CLI flags. | |
| 2053 | args_len: u29, | |
| 2054 | lang: OptionalCSourceLanguage, | |
| 2055 | }; | |
| 2056 | }; | |
| 2057 | ||
| 2058 | pub const RcSourceFile = struct { | |
| 2059 | flags: Flags, | |
| 2060 | file: LazyPath.Index, | |
| 2061 | args: Storage.FlagList(.flags, .args_len, String), | |
| 2062 | include_paths: Storage.FlagLengthPrefixedList(.flags, .include_paths, LazyPath.Index), | |
| 2063 | ||
| 2064 | pub const Index = enum(u32) { | |
| 2065 | _, | |
| 2066 | ||
| 2067 | pub fn get(this: @This(), c: *const Configuration) RcSourceFile { | |
| 2068 | return extraData(c, RcSourceFile, @intFromEnum(this)); | |
| 2069 | } | |
| 2070 | }; | |
| 2071 | ||
| 2072 | pub const Flags = packed struct(u32) { | |
| 2073 | /// C compiler CLI flags. | |
| 2074 | args_len: u31, | |
| 2075 | include_paths: bool, | |
| 2076 | }; | |
| 2077 | }; | |
| 2078 | ||
| 2079 | pub const OptionalCSourceLanguage = enum(u3) { | |
| 2080 | c, | |
| 2081 | cpp, | |
| 2082 | objective_c, | |
| 2083 | objective_cpp, | |
| 2084 | assembly, | |
| 2085 | assembly_with_preprocessor, | |
| 2086 | default, | |
| 2087 | ||
| 2088 | pub fn init(x: ?std.Build.Module.CSourceLanguage) @This() { | |
| 2089 | return switch (x orelse return .default) { | |
| 2090 | .c => .c, | |
| 2091 | .cpp => .cpp, | |
| 2092 | .objective_c => .objective_c, | |
| 2093 | .objective_cpp => .objective_cpp, | |
| 2094 | .assembly => .assembly, | |
| 2095 | .assembly_with_preprocessor => .assembly_with_preprocessor, | |
| 2096 | }; | |
| 2097 | } | |
| 2098 | ||
| 2099 | pub fn get(this: @This()) ?std.Build.Module.CSourceLanguage { | |
| 2100 | return switch (this) { | |
| 2101 | .c => .c, | |
| 2102 | .cpp => .cpp, | |
| 2103 | .objective_c => .objective_c, | |
| 2104 | .objective_cpp => .objective_cpp, | |
| 2105 | .assembly => .assembly, | |
| 2106 | .assembly_with_preprocessor => .assembly_with_preprocessor, | |
| 2107 | .default => null, | |
| 2108 | }; | |
| 2109 | } | |
| 2110 | }; | |
| 2111 | ||
| 2112 | pub const ResolvedTarget = struct { | |
| 2113 | /// none indicates host. | |
| 2114 | query: TargetQuery.OptionalIndex, | |
| 2115 | /// defaults will be resolved. | |
| 2116 | result: TargetQuery.Index, | |
| 2117 | ||
| 2118 | pub const Index = enum(u32) { | |
| 2119 | _, | |
| 2120 | ||
| 2121 | pub fn get(this: @This(), c: *const Configuration) ResolvedTarget { | |
| 2122 | return extraData(c, ResolvedTarget, @intFromEnum(this)); | |
| 2123 | } | |
| 2124 | }; | |
| 2125 | ||
| 2126 | pub const OptionalIndex = enum(u32) { | |
| 2127 | none = max_u32, | |
| 2128 | _, | |
| 2129 | ||
| 2130 | pub fn init(i: Index) OptionalIndex { | |
| 2131 | const result: OptionalIndex = @enumFromInt(@intFromEnum(i)); | |
| 2132 | assert(result != .none); | |
| 2133 | return result; | |
| 2134 | } | |
| 2135 | ||
| 2136 | pub fn unwrap(this: @This()) ?Index { | |
| 2137 | return switch (this) { | |
| 2138 | .none => null, | |
| 2139 | _ => @enumFromInt(@intFromEnum(this)), | |
| 2140 | }; | |
| 2141 | } | |
| 2142 | ||
| 2143 | pub fn get(this: @This(), c: *const Configuration) ?ResolvedTarget { | |
| 2144 | return (unwrap(this) orelse return null).get(c); | |
| 2145 | } | |
| 2146 | }; | |
| 2147 | }; | |
| 2148 | ||
| 2149 | pub const TargetQuery = struct { | |
| 2150 | flags: Flags, | |
| 2151 | ||
| 2152 | cpu_features_add: Storage.FlagOptional(.flags, .cpu_features_add, std.Target.Cpu.Feature.Set), | |
| 2153 | cpu_features_sub: Storage.FlagOptional(.flags, .cpu_features_sub, std.Target.Cpu.Feature.Set), | |
| 2154 | cpu_name: Storage.EnumOptional(.flags, .cpu_model, .explicit, String), | |
| 2155 | os_version_min: Storage.FlagUnion(.flags, .os_version_min, OsVersion), | |
| 2156 | os_version_max: Storage.FlagUnion(.flags, .os_version_max, OsVersion), | |
| 2157 | glibc_version: Storage.FlagOptional(.flags, .glibc_version, String), | |
| 2158 | android_api_level: Storage.FlagOptional(.flags, .android_api_level, u32), | |
| 2159 | dynamic_linker: Storage.FlagOptional(.flags, .dynamic_linker, String), | |
| 2160 | ||
| 2161 | pub const Index = enum(u32) { | |
| 2162 | _, | |
| 2163 | ||
| 2164 | pub fn extraSlice(i: Index, extra: []const u32) []const u32 { | |
| 2165 | return extra[@intFromEnum(i)..][0..length(i, extra)]; | |
| 2166 | } | |
| 2167 | ||
| 2168 | pub fn length(i: Index, extra: []const u32) usize { | |
| 2169 | return Storage.dataLength(extra, @intFromEnum(i), TargetQuery); | |
| 2170 | } | |
| 2171 | ||
| 2172 | pub fn get(this: @This(), c: *const Configuration) TargetQuery { | |
| 2173 | return extraData(c, TargetQuery, @intFromEnum(this)); | |
| 2174 | } | |
| 2175 | }; | |
| 2176 | ||
| 2177 | pub const OptionalIndex = enum(u32) { | |
| 2178 | none = max_u32, | |
| 2179 | _, | |
| 2180 | ||
| 2181 | pub fn init(i: Index) OptionalIndex { | |
| 2182 | const result: OptionalIndex = @enumFromInt(@intFromEnum(i)); | |
| 2183 | assert(result != .none); | |
| 2184 | return result; | |
| 2185 | } | |
| 2186 | ||
| 2187 | pub fn unwrap(this: @This()) ?Index { | |
| 2188 | return switch (this) { | |
| 2189 | .none => null, | |
| 2190 | _ => @enumFromInt(@intFromEnum(this)), | |
| 2191 | }; | |
| 2192 | } | |
| 2193 | ||
| 2194 | pub fn get(this: @This(), c: *const Configuration) ?TargetQuery { | |
| 2195 | return (this.unwrap() orelse return null).get(c); | |
| 2196 | } | |
| 2197 | }; | |
| 2198 | ||
| 2199 | pub const CpuModel = enum(u2) { | |
| 2200 | native, | |
| 2201 | baseline, | |
| 2202 | determined_by_arch_os, | |
| 2203 | explicit, | |
| 2204 | ||
| 2205 | pub fn init(x: std.Target.Query.CpuModel) @This() { | |
| 2206 | return switch (x) { | |
| 2207 | .native => .native, | |
| 2208 | .baseline => .baseline, | |
| 2209 | .determined_by_arch_os => .determined_by_arch_os, | |
| 2210 | .explicit => .explicit, | |
| 2211 | }; | |
| 2212 | } | |
| 2213 | }; | |
| 2214 | pub const OsVersion = union(@This().Tag) { | |
| 2215 | pub const Tag = enum(u2) { none, semver, windows, default }; | |
| 2216 | ||
| 2217 | none: void, | |
| 2218 | semver: String, | |
| 2219 | windows: std.Target.Os.WindowsVersion, | |
| 2220 | default: void, | |
| 2221 | ||
| 2222 | pub fn init(x: ?std.Target.Query.OsVersion) @This() { | |
| 2223 | return switch (x orelse return .default) { | |
| 2224 | .none => .none, | |
| 2225 | .semver => .semver, | |
| 2226 | .windows => .windows, | |
| 2227 | }; | |
| 2228 | } | |
| 2229 | ||
| 2230 | pub fn unwrap(this: @This(), c: *const Configuration) ?std.Target.Query.OsVersion { | |
| 2231 | return switch (this) { | |
| 2232 | .none => .none, | |
| 2233 | .semver => |sv| .{ .semver = std.SemanticVersion.parse(sv.slice(c)) catch unreachable }, | |
| 2234 | .windows => |wv| .{ .windows = wv }, | |
| 2235 | .default => null, | |
| 2236 | }; | |
| 2237 | } | |
| 2238 | }; | |
| 2239 | ||
| 2240 | pub const Abi = enum(u5) { | |
| 2241 | none, | |
| 2242 | gnu, | |
| 2243 | gnuabin32, | |
| 2244 | gnuabi64, | |
| 2245 | gnueabi, | |
| 2246 | gnueabihf, | |
| 2247 | gnuf32, | |
| 2248 | gnusf, | |
| 2249 | gnux32, | |
| 2250 | eabi, | |
| 2251 | eabihf, | |
| 2252 | ilp32, | |
| 2253 | android, | |
| 2254 | androideabi, | |
| 2255 | musl, | |
| 2256 | muslabin32, | |
| 2257 | muslabi64, | |
| 2258 | musleabi, | |
| 2259 | musleabihf, | |
| 2260 | muslf32, | |
| 2261 | muslsf, | |
| 2262 | muslx32, | |
| 2263 | msvc, | |
| 2264 | itanium, | |
| 2265 | simulator, | |
| 2266 | ohos, | |
| 2267 | ohoseabi, | |
| 2268 | call0, | |
| 2269 | ||
| 2270 | default, | |
| 2271 | ||
| 2272 | pub fn init(x: ?std.Target.Abi) @This() { | |
| 2273 | return switch (x orelse return .default) { | |
| 2274 | .none => .none, | |
| 2275 | .gnu => .gnu, | |
| 2276 | .gnuabin32 => .gnuabin32, | |
| 2277 | .gnuabi64 => .gnuabi64, | |
| 2278 | .gnueabi => .gnueabi, | |
| 2279 | .gnueabihf => .gnueabihf, | |
| 2280 | .gnuf32 => .gnuf32, | |
| 2281 | .gnusf => .gnusf, | |
| 2282 | .gnux32 => .gnux32, | |
| 2283 | .eabi => .eabi, | |
| 2284 | .eabihf => .eabihf, | |
| 2285 | .ilp32 => .ilp32, | |
| 2286 | .android => .android, | |
| 2287 | .androideabi => .androideabi, | |
| 2288 | .musl => .musl, | |
| 2289 | .muslabin32 => .muslabin32, | |
| 2290 | .muslabi64 => .muslabi64, | |
| 2291 | .musleabi => .musleabi, | |
| 2292 | .musleabihf => .musleabihf, | |
| 2293 | .muslf32 => .muslf32, | |
| 2294 | .muslsf => .muslsf, | |
| 2295 | .muslx32 => .muslx32, | |
| 2296 | .msvc => .msvc, | |
| 2297 | .itanium => .itanium, | |
| 2298 | .simulator => .simulator, | |
| 2299 | .ohos => .ohos, | |
| 2300 | .ohoseabi => .ohoseabi, | |
| 2301 | .call0 => .call0, | |
| 2302 | }; | |
| 2303 | } | |
| 2304 | ||
| 2305 | pub fn unwrap(this: @This()) ?std.Target.Abi { | |
| 2306 | return switch (this) { | |
| 2307 | .none => .none, | |
| 2308 | .gnu => .gnu, | |
| 2309 | .gnuabin32 => .gnuabin32, | |
| 2310 | .gnuabi64 => .gnuabi64, | |
| 2311 | .gnueabi => .gnueabi, | |
| 2312 | .gnueabihf => .gnueabihf, | |
| 2313 | .gnuf32 => .gnuf32, | |
| 2314 | .gnusf => .gnusf, | |
| 2315 | .gnux32 => .gnux32, | |
| 2316 | .eabi => .eabi, | |
| 2317 | .eabihf => .eabihf, | |
| 2318 | .ilp32 => .ilp32, | |
| 2319 | .android => .android, | |
| 2320 | .androideabi => .androideabi, | |
| 2321 | .musl => .musl, | |
| 2322 | .muslabin32 => .muslabin32, | |
| 2323 | .muslabi64 => .muslabi64, | |
| 2324 | .musleabi => .musleabi, | |
| 2325 | .musleabihf => .musleabihf, | |
| 2326 | .muslf32 => .muslf32, | |
| 2327 | .muslsf => .muslsf, | |
| 2328 | .muslx32 => .muslx32, | |
| 2329 | .msvc => .msvc, | |
| 2330 | .itanium => .itanium, | |
| 2331 | .simulator => .simulator, | |
| 2332 | .ohos => .ohos, | |
| 2333 | .ohoseabi => .ohoseabi, | |
| 2334 | .call0 => .call0, | |
| 2335 | .default => null, | |
| 2336 | }; | |
| 2337 | } | |
| 2338 | }; | |
| 2339 | ||
| 2340 | pub const CpuArch = enum(u6) { | |
| 2341 | aarch64, | |
| 2342 | aarch64_be, | |
| 2343 | alpha, | |
| 2344 | amdgcn, | |
| 2345 | arc, | |
| 2346 | arceb, | |
| 2347 | arm, | |
| 2348 | armeb, | |
| 2349 | avr, | |
| 2350 | bpfeb, | |
| 2351 | bpfel, | |
| 2352 | csky, | |
| 2353 | ez80, | |
| 2354 | hexagon, | |
| 2355 | hppa, | |
| 2356 | hppa64, | |
| 2357 | kalimba, | |
| 2358 | kvx, | |
| 2359 | lanai, | |
| 2360 | loongarch32, | |
| 2361 | loongarch64, | |
| 2362 | m68k, | |
| 2363 | m88k, | |
| 2364 | microblaze, | |
| 2365 | microblazeel, | |
| 2366 | mips, | |
| 2367 | mipsel, | |
| 2368 | mips64, | |
| 2369 | mips64el, | |
| 2370 | msp430, | |
| 2371 | nvptx, | |
| 2372 | nvptx64, | |
| 2373 | or1k, | |
| 2374 | powerpc, | |
| 2375 | powerpcle, | |
| 2376 | powerpc64, | |
| 2377 | powerpc64le, | |
| 2378 | propeller, | |
| 2379 | riscv32, | |
| 2380 | riscv32be, | |
| 2381 | riscv64, | |
| 2382 | riscv64be, | |
| 2383 | s390x, | |
| 2384 | sh, | |
| 2385 | sheb, | |
| 2386 | sparc, | |
| 2387 | sparc64, | |
| 2388 | spirv32, | |
| 2389 | spirv64, | |
| 2390 | thumb, | |
| 2391 | thumbeb, | |
| 2392 | ve, | |
| 2393 | wasm32, | |
| 2394 | wasm64, | |
| 2395 | x86_16, | |
| 2396 | x86, | |
| 2397 | x86_64, | |
| 2398 | xcore, | |
| 2399 | xtensa, | |
| 2400 | xtensaeb, | |
| 2401 | ||
| 2402 | default, | |
| 2403 | ||
| 2404 | pub fn init(x: ?std.Target.Cpu.Arch) @This() { | |
| 2405 | return switch (x orelse return .default) { | |
| 2406 | .aarch64 => .aarch64, | |
| 2407 | .aarch64_be => .aarch64_be, | |
| 2408 | .alpha => .alpha, | |
| 2409 | .amdgcn => .amdgcn, | |
| 2410 | .arc => .arc, | |
| 2411 | .arceb => .arceb, | |
| 2412 | .arm => .arm, | |
| 2413 | .armeb => .armeb, | |
| 2414 | .avr => .avr, | |
| 2415 | .bpfeb => .bpfeb, | |
| 2416 | .bpfel => .bpfel, | |
| 2417 | .csky => .csky, | |
| 2418 | .ez80 => .ez80, | |
| 2419 | .hexagon => .hexagon, | |
| 2420 | .hppa => .hppa, | |
| 2421 | .hppa64 => .hppa64, | |
| 2422 | .kalimba => .kalimba, | |
| 2423 | .kvx => .kvx, | |
| 2424 | .lanai => .lanai, | |
| 2425 | .loongarch32 => .loongarch32, | |
| 2426 | .loongarch64 => .loongarch64, | |
| 2427 | .m68k => .m68k, | |
| 2428 | .m88k => .m88k, | |
| 2429 | .microblaze => .microblaze, | |
| 2430 | .microblazeel => .microblazeel, | |
| 2431 | .mips => .mips, | |
| 2432 | .mipsel => .mipsel, | |
| 2433 | .mips64 => .mips64, | |
| 2434 | .mips64el => .mips64el, | |
| 2435 | .msp430 => .msp430, | |
| 2436 | .nvptx => .nvptx, | |
| 2437 | .nvptx64 => .nvptx64, | |
| 2438 | .or1k => .or1k, | |
| 2439 | .powerpc => .powerpc, | |
| 2440 | .powerpcle => .powerpcle, | |
| 2441 | .powerpc64 => .powerpc64, | |
| 2442 | .powerpc64le => .powerpc64le, | |
| 2443 | .propeller => .propeller, | |
| 2444 | .riscv32 => .riscv32, | |
| 2445 | .riscv32be => .riscv32be, | |
| 2446 | .riscv64 => .riscv64, | |
| 2447 | .riscv64be => .riscv64be, | |
| 2448 | .s390x => .s390x, | |
| 2449 | .sh => .sh, | |
| 2450 | .sheb => .sheb, | |
| 2451 | .sparc => .sparc, | |
| 2452 | .sparc64 => .sparc64, | |
| 2453 | .spirv32 => .spirv32, | |
| 2454 | .spirv64 => .spirv64, | |
| 2455 | .thumb => .thumb, | |
| 2456 | .thumbeb => .thumbeb, | |
| 2457 | .ve => .ve, | |
| 2458 | .wasm32 => .wasm32, | |
| 2459 | .wasm64 => .wasm64, | |
| 2460 | .x86_16 => .x86_16, | |
| 2461 | .x86 => .x86, | |
| 2462 | .x86_64 => .x86_64, | |
| 2463 | .xcore => .xcore, | |
| 2464 | .xtensa => .xtensa, | |
| 2465 | .xtensaeb => .xtensaeb, | |
| 2466 | }; | |
| 2467 | } | |
| 2468 | ||
| 2469 | pub fn unwrap(this: @This()) ?std.Target.Cpu.Arch { | |
| 2470 | return switch (this) { | |
| 2471 | .aarch64 => .aarch64, | |
| 2472 | .aarch64_be => .aarch64_be, | |
| 2473 | .alpha => .alpha, | |
| 2474 | .amdgcn => .amdgcn, | |
| 2475 | .arc => .arc, | |
| 2476 | .arceb => .arceb, | |
| 2477 | .arm => .arm, | |
| 2478 | .armeb => .armeb, | |
| 2479 | .avr => .avr, | |
| 2480 | .bpfeb => .bpfeb, | |
| 2481 | .bpfel => .bpfel, | |
| 2482 | .csky => .csky, | |
| 2483 | .ez80 => .ez80, | |
| 2484 | .hexagon => .hexagon, | |
| 2485 | .hppa => .hppa, | |
| 2486 | .hppa64 => .hppa64, | |
| 2487 | .kalimba => .kalimba, | |
| 2488 | .kvx => .kvx, | |
| 2489 | .lanai => .lanai, | |
| 2490 | .loongarch32 => .loongarch32, | |
| 2491 | .loongarch64 => .loongarch64, | |
| 2492 | .m68k => .m68k, | |
| 2493 | .m88k => .m88k, | |
| 2494 | .microblaze => .microblaze, | |
| 2495 | .microblazeel => .microblazeel, | |
| 2496 | .mips => .mips, | |
| 2497 | .mipsel => .mipsel, | |
| 2498 | .mips64 => .mips64, | |
| 2499 | .mips64el => .mips64el, | |
| 2500 | .msp430 => .msp430, | |
| 2501 | .nvptx => .nvptx, | |
| 2502 | .nvptx64 => .nvptx64, | |
| 2503 | .or1k => .or1k, | |
| 2504 | .powerpc => .powerpc, | |
| 2505 | .powerpcle => .powerpcle, | |
| 2506 | .powerpc64 => .powerpc64, | |
| 2507 | .powerpc64le => .powerpc64le, | |
| 2508 | .propeller => .propeller, | |
| 2509 | .riscv32 => .riscv32, | |
| 2510 | .riscv32be => .riscv32be, | |
| 2511 | .riscv64 => .riscv64, | |
| 2512 | .riscv64be => .riscv64be, | |
| 2513 | .s390x => .s390x, | |
| 2514 | .sh => .sh, | |
| 2515 | .sheb => .sheb, | |
| 2516 | .sparc => .sparc, | |
| 2517 | .sparc64 => .sparc64, | |
| 2518 | .spirv32 => .spirv32, | |
| 2519 | .spirv64 => .spirv64, | |
| 2520 | .thumb => .thumb, | |
| 2521 | .thumbeb => .thumbeb, | |
| 2522 | .ve => .ve, | |
| 2523 | .wasm32 => .wasm32, | |
| 2524 | .wasm64 => .wasm64, | |
| 2525 | .x86_16 => .x86_16, | |
| 2526 | .x86 => .x86, | |
| 2527 | .x86_64 => .x86_64, | |
| 2528 | .xcore => .xcore, | |
| 2529 | .xtensa => .xtensa, | |
| 2530 | .xtensaeb => .xtensaeb, | |
| 2531 | ||
| 2532 | .default => null, | |
| 2533 | }; | |
| 2534 | } | |
| 2535 | }; | |
| 2536 | ||
| 2537 | pub const OsTag = enum(u6) { | |
| 2538 | freestanding, | |
| 2539 | other, | |
| 2540 | contiki, | |
| 2541 | fuchsia, | |
| 2542 | hermit, | |
| 2543 | managarm, | |
| 2544 | haiku, | |
| 2545 | hurd, | |
| 2546 | illumos, | |
| 2547 | linux, | |
| 2548 | plan9, | |
| 2549 | rtems, | |
| 2550 | serenity, | |
| 2551 | dragonfly, | |
| 2552 | freebsd, | |
| 2553 | netbsd, | |
| 2554 | openbsd, | |
| 2555 | driverkit, | |
| 2556 | ios, | |
| 2557 | maccatalyst, | |
| 2558 | macos, | |
| 2559 | tvos, | |
| 2560 | visionos, | |
| 2561 | watchos, | |
| 2562 | windows, | |
| 2563 | uefi, | |
| 2564 | @"3ds", | |
| 2565 | ps3, | |
| 2566 | ps4, | |
| 2567 | ps5, | |
| 2568 | psp, | |
| 2569 | vita, | |
| 2570 | emscripten, | |
| 2571 | wasi, | |
| 2572 | amdhsa, | |
| 2573 | amdpal, | |
| 2574 | cuda, | |
| 2575 | mesa3d, | |
| 2576 | nvcl, | |
| 2577 | opencl, | |
| 2578 | opengl, | |
| 2579 | vulkan, | |
| 2580 | tios, | |
| 2581 | ||
| 2582 | default, | |
| 2583 | ||
| 2584 | pub fn init(x: ?std.Target.Os.Tag) @This() { | |
| 2585 | return switch (x orelse return .default) { | |
| 2586 | .freestanding => .freestanding, | |
| 2587 | .other => .other, | |
| 2588 | .contiki => .contiki, | |
| 2589 | .fuchsia => .fuchsia, | |
| 2590 | .hermit => .hermit, | |
| 2591 | .managarm => .managarm, | |
| 2592 | .haiku => .haiku, | |
| 2593 | .hurd => .hurd, | |
| 2594 | .illumos => .illumos, | |
| 2595 | .linux => .linux, | |
| 2596 | .plan9 => .plan9, | |
| 2597 | .rtems => .rtems, | |
| 2598 | .serenity => .serenity, | |
| 2599 | .dragonfly => .dragonfly, | |
| 2600 | .freebsd => .freebsd, | |
| 2601 | .netbsd => .netbsd, | |
| 2602 | .openbsd => .openbsd, | |
| 2603 | .driverkit => .driverkit, | |
| 2604 | .ios => .ios, | |
| 2605 | .maccatalyst => .maccatalyst, | |
| 2606 | .macos => .macos, | |
| 2607 | .tvos => .tvos, | |
| 2608 | .visionos => .visionos, | |
| 2609 | .watchos => .watchos, | |
| 2610 | .windows => .windows, | |
| 2611 | .uefi => .uefi, | |
| 2612 | .@"3ds" => .@"3ds", | |
| 2613 | .ps3 => .ps3, | |
| 2614 | .ps4 => .ps4, | |
| 2615 | .ps5 => .ps5, | |
| 2616 | .psp => .psp, | |
| 2617 | .vita => .vita, | |
| 2618 | .emscripten => .emscripten, | |
| 2619 | .wasi => .wasi, | |
| 2620 | .amdhsa => .amdhsa, | |
| 2621 | .amdpal => .amdpal, | |
| 2622 | .cuda => .cuda, | |
| 2623 | .mesa3d => .mesa3d, | |
| 2624 | .nvcl => .nvcl, | |
| 2625 | .opencl => .opencl, | |
| 2626 | .opengl => .opengl, | |
| 2627 | .vulkan => .vulkan, | |
| 2628 | .tios => .tios, | |
| 2629 | }; | |
| 2630 | } | |
| 2631 | ||
| 2632 | pub fn unwrap(this: @This()) ?std.Target.Os.Tag { | |
| 2633 | return switch (this) { | |
| 2634 | .freestanding => .freestanding, | |
| 2635 | .other => .other, | |
| 2636 | .contiki => .contiki, | |
| 2637 | .fuchsia => .fuchsia, | |
| 2638 | .hermit => .hermit, | |
| 2639 | .managarm => .managarm, | |
| 2640 | .haiku => .haiku, | |
| 2641 | .hurd => .hurd, | |
| 2642 | .illumos => .illumos, | |
| 2643 | .linux => .linux, | |
| 2644 | .plan9 => .plan9, | |
| 2645 | .rtems => .rtems, | |
| 2646 | .serenity => .serenity, | |
| 2647 | .dragonfly => .dragonfly, | |
| 2648 | .freebsd => .freebsd, | |
| 2649 | .netbsd => .netbsd, | |
| 2650 | .openbsd => .openbsd, | |
| 2651 | .driverkit => .driverkit, | |
| 2652 | .ios => .ios, | |
| 2653 | .maccatalyst => .maccatalyst, | |
| 2654 | .macos => .macos, | |
| 2655 | .tvos => .tvos, | |
| 2656 | .visionos => .visionos, | |
| 2657 | .watchos => .watchos, | |
| 2658 | .windows => .windows, | |
| 2659 | .uefi => .uefi, | |
| 2660 | .@"3ds" => .@"3ds", | |
| 2661 | .ps3 => .ps3, | |
| 2662 | .ps4 => .ps4, | |
| 2663 | .ps5 => .ps5, | |
| 2664 | .psp => .psp, | |
| 2665 | .vita => .vita, | |
| 2666 | .emscripten => .emscripten, | |
| 2667 | .wasi => .wasi, | |
| 2668 | .amdhsa => .amdhsa, | |
| 2669 | .amdpal => .amdpal, | |
| 2670 | .cuda => .cuda, | |
| 2671 | .mesa3d => .mesa3d, | |
| 2672 | .nvcl => .nvcl, | |
| 2673 | .opencl => .opencl, | |
| 2674 | .opengl => .opengl, | |
| 2675 | .vulkan => .vulkan, | |
| 2676 | .tios => .tios, | |
| 2677 | ||
| 2678 | .default => null, | |
| 2679 | }; | |
| 2680 | } | |
| 2681 | }; | |
| 2682 | ||
| 2683 | pub const ObjectFormat = enum(u4) { | |
| 2684 | c, | |
| 2685 | coff, | |
| 2686 | elf, | |
| 2687 | hex, | |
| 2688 | macho, | |
| 2689 | plan9, | |
| 2690 | raw, | |
| 2691 | spirv, | |
| 2692 | wasm, | |
| 2693 | ||
| 2694 | default, | |
| 2695 | ||
| 2696 | pub fn init(x: ?std.Target.ObjectFormat) @This() { | |
| 2697 | return switch (x orelse return .default) { | |
| 2698 | .c => .c, | |
| 2699 | .coff => .coff, | |
| 2700 | .elf => .elf, | |
| 2701 | .hex => .hex, | |
| 2702 | .macho => .macho, | |
| 2703 | .plan9 => .plan9, | |
| 2704 | .raw => .raw, | |
| 2705 | .spirv => .spirv, | |
| 2706 | .wasm => .wasm, | |
| 2707 | }; | |
| 2708 | } | |
| 2709 | ||
| 2710 | pub fn unwrap(this: @This()) ?std.Target.ObjectFormat { | |
| 2711 | return switch (this) { | |
| 2712 | .c => .c, | |
| 2713 | .coff => .coff, | |
| 2714 | .elf => .elf, | |
| 2715 | .hex => .hex, | |
| 2716 | .macho => .macho, | |
| 2717 | .plan9 => .plan9, | |
| 2718 | .raw => .raw, | |
| 2719 | .spirv => .spirv, | |
| 2720 | .wasm => .wasm, | |
| 2721 | ||
| 2722 | .default => null, | |
| 2723 | }; | |
| 2724 | } | |
| 2725 | }; | |
| 2726 | ||
| 2727 | pub const Flags = packed struct(u32) { | |
| 2728 | cpu_arch: CpuArch, | |
| 2729 | cpu_model: CpuModel, | |
| 2730 | cpu_features_add: bool, | |
| 2731 | cpu_features_sub: bool, | |
| 2732 | os_tag: OsTag, | |
| 2733 | abi: Abi, | |
| 2734 | object_format: ObjectFormat, | |
| 2735 | os_version_min: OsVersion.Tag, | |
| 2736 | os_version_max: OsVersion.Tag, | |
| 2737 | glibc_version: bool, | |
| 2738 | android_api_level: bool, | |
| 2739 | dynamic_linker: bool, | |
| 2740 | }; | |
| 2741 | ||
| 2742 | pub fn unwrap(tq: *const TargetQuery, c: *const Configuration) std.Target.Query { | |
| 2743 | const cpu_arch = tq.flags.cpu_arch.unwrap(); | |
| 2744 | return .{ | |
| 2745 | .cpu_arch = cpu_arch, | |
| 2746 | .cpu_model = switch (tq.flags.cpu_model) { | |
| 2747 | .native => .native, | |
| 2748 | .baseline => .baseline, | |
| 2749 | .determined_by_arch_os => .determined_by_arch_os, | |
| 2750 | .explicit => .{ .explicit = cpu_arch.?.parseCpuModel(tq.cpu_name.value.?.slice(c)).? }, | |
| 2751 | }, | |
| 2752 | .cpu_features_add = tq.cpu_features_add.value orelse .empty, | |
| 2753 | .cpu_features_sub = tq.cpu_features_sub.value orelse .empty, | |
| 2754 | .os_tag = tq.flags.os_tag.unwrap(), | |
| 2755 | .os_version_min = tq.os_version_min.u.unwrap(c), | |
| 2756 | .os_version_max = tq.os_version_max.u.unwrap(c), | |
| 2757 | .glibc_version = if (tq.glibc_version.value) |s| | |
| 2758 | std.SemanticVersion.parse(s.slice(c)) catch unreachable | |
| 2759 | else | |
| 2760 | null, | |
| 2761 | .android_api_level = tq.android_api_level.value, | |
| 2762 | .abi = tq.flags.abi.unwrap(), | |
| 2763 | .dynamic_linker = if (tq.dynamic_linker.value) |s| .init(s.slice(c)) else null, | |
| 2764 | .ofmt = tq.flags.object_format.unwrap(), | |
| 2765 | }; | |
| 2766 | } | |
| 2767 | }; | |
| 2768 | ||
| 2769 | pub const Storage = enum { | |
| 2770 | flag_optional, | |
| 2771 | enum_optional, | |
| 2772 | extended, | |
| 2773 | length_prefixed_list, | |
| 2774 | flag_length_prefixed_list, | |
| 2775 | union_list, | |
| 2776 | flag_union, | |
| 2777 | multi_list, | |
| 2778 | flag_list, | |
| 2779 | ||
| 2780 | /// The presence of the field is determined by a boolean within a packed | |
| 2781 | /// struct. | |
| 2782 | pub fn FlagOptional( | |
| 2783 | comptime flags_arg: @EnumLiteral(), | |
| 2784 | comptime flag_arg: @EnumLiteral(), | |
| 2785 | comptime ValueArg: type, | |
| 2786 | ) type { | |
| 2787 | return struct { | |
| 2788 | value: ?Value, | |
| 2789 | ||
| 2790 | pub const storage: Storage = .flag_optional; | |
| 2791 | pub const flags = flags_arg; | |
| 2792 | pub const flag = flag_arg; | |
| 2793 | pub const Value = ValueArg; | |
| 2794 | }; | |
| 2795 | } | |
| 2796 | ||
| 2797 | /// The type of the field is determined by an enum within a packed struct. | |
| 2798 | pub fn FlagUnion( | |
| 2799 | comptime flags_arg: @EnumLiteral(), | |
| 2800 | comptime flag_arg: @EnumLiteral(), | |
| 2801 | comptime UnionArg: type, | |
| 2802 | ) type { | |
| 2803 | return struct { | |
| 2804 | u: Union, | |
| 2805 | ||
| 2806 | pub const storage: Storage = .flag_union; | |
| 2807 | pub const flags = flags_arg; | |
| 2808 | pub const flag = flag_arg; | |
| 2809 | pub const Union = UnionArg; | |
| 2810 | ||
| 2811 | pub const Tag = @typeInfo(Union).@"union".tag_type.?; | |
| 2812 | }; | |
| 2813 | } | |
| 2814 | ||
| 2815 | /// The field is present if an enum tag from flags matches a specific value. | |
| 2816 | pub fn EnumOptional( | |
| 2817 | comptime flags_arg: @EnumLiteral(), | |
| 2818 | comptime flag_arg: @EnumLiteral(), | |
| 2819 | comptime tag_arg: @EnumLiteral(), | |
| 2820 | comptime ValueArg: type, | |
| 2821 | ) type { | |
| 2822 | return struct { | |
| 2823 | value: ?Value, | |
| 2824 | ||
| 2825 | pub const storage: Storage = .enum_optional; | |
| 2826 | pub const flags = flags_arg; | |
| 2827 | pub const flag = flag_arg; | |
| 2828 | pub const tag = tag_arg; | |
| 2829 | pub const Value = ValueArg; | |
| 2830 | }; | |
| 2831 | } | |
| 2832 | ||
| 2833 | /// The field indexes into an auxilary buffer, with the first element being | |
| 2834 | /// a packed struct that contains the tag. | |
| 2835 | pub fn Extended(comptime BaseFlags: type, comptime U: type) type { | |
| 2836 | return enum(u32) { | |
| 2837 | _, | |
| 2838 | ||
| 2839 | pub const storage: Storage = .extended; | |
| 2840 | ||
| 2841 | pub fn tag(this: @This(), c: *const Configuration) @FieldType(BaseFlags, "tag") { | |
| 2842 | const base_flags: BaseFlags = @bitCast(c.extra[@intFromEnum(this)]); | |
| 2843 | return base_flags.tag; | |
| 2844 | } | |
| 2845 | ||
| 2846 | pub fn cast(this: @This(), c: *const Configuration, comptime S: type) ?S { | |
| 2847 | const wanted_tag = @typeInfo(S.Flags).@"struct".fields[0].defaultValue().?; | |
| 2848 | const base_flags: BaseFlags = @bitCast(c.extra[@intFromEnum(this)]); | |
| 2849 | if (base_flags.tag != wanted_tag) return null; | |
| 2850 | var i: usize = @intFromEnum(this); | |
| 2851 | return data(c.extra, &i, S); | |
| 2852 | } | |
| 2853 | ||
| 2854 | pub fn get(this: @This(), buffer: []const u32) U { | |
| 2855 | var i: usize = @intFromEnum(this); | |
| 2856 | const base_flags: BaseFlags = @bitCast(buffer[i]); | |
| 2857 | return switch (base_flags.tag) { | |
| 2858 | inline else => |t| @unionInit(U, @tagName(t), data(buffer, &i, @FieldType(U, @tagName(t)))), | |
| 2859 | }; | |
| 2860 | } | |
| 2861 | }; | |
| 2862 | } | |
| 2863 | ||
| 2864 | /// A field in flags determines whether the length is zero or nonzero. If | |
| 2865 | /// the length is nonzero, then there is a length field followed by the | |
| 2866 | /// list. The elements need well-defined memory layout but can otherwise be | |
| 2867 | /// any multiple of u32 length. The length is the number of elements, not | |
| 2868 | /// the number of u32s. | |
| 2869 | pub fn FlagLengthPrefixedList( | |
| 2870 | comptime flags_arg: @EnumLiteral(), | |
| 2871 | comptime flag_arg: @EnumLiteral(), | |
| 2872 | comptime ElemArg: type, | |
| 2873 | ) type { | |
| 2874 | return struct { | |
| 2875 | slice: []const Elem, | |
| 2876 | ||
| 2877 | pub const storage: Storage = .flag_length_prefixed_list; | |
| 2878 | pub const flags = flags_arg; | |
| 2879 | pub const flag = flag_arg; | |
| 2880 | pub const Elem = ElemArg; | |
| 2881 | ||
| 2882 | pub fn initErased(s: []const u32) @This() { | |
| 2883 | return .{ .slice = @ptrCast(s) }; | |
| 2884 | } | |
| 2885 | }; | |
| 2886 | } | |
| 2887 | ||
| 2888 | /// The field contains a u32 length followed by that many items. Each | |
| 2889 | /// element needs well-defined memory layout but can otherwise be any | |
| 2890 | /// multiple of u32 length. The length is number of elements, not the | |
| 2891 | /// number of u32s. | |
| 2892 | pub fn LengthPrefixedList(comptime ElemArg: type) type { | |
| 2893 | return struct { | |
| 2894 | slice: []const Elem, | |
| 2895 | ||
| 2896 | pub const storage: Storage = .length_prefixed_list; | |
| 2897 | pub const Elem = ElemArg; | |
| 2898 | ||
| 2899 | pub fn initErased(s: []const u32) @This() { | |
| 2900 | return .{ .slice = @ptrCast(s) }; | |
| 2901 | } | |
| 2902 | }; | |
| 2903 | } | |
| 2904 | ||
| 2905 | /// The field is a list whose length is an integer inside flags. | |
| 2906 | pub fn FlagList( | |
| 2907 | comptime flags_arg: @EnumLiteral(), | |
| 2908 | comptime flag_arg: @EnumLiteral(), | |
| 2909 | comptime ElemArg: type, | |
| 2910 | ) type { | |
| 2911 | return struct { | |
| 2912 | slice: []const Elem, | |
| 2913 | ||
| 2914 | pub const storage: Storage = .flag_list; | |
| 2915 | pub const flags = flags_arg; | |
| 2916 | pub const flag = flag_arg; | |
| 2917 | pub const Elem = ElemArg; | |
| 2918 | ||
| 2919 | pub fn initErased(s: []const u32) @This() { | |
| 2920 | return .{ .slice = @ptrCast(s) }; | |
| 2921 | } | |
| 2922 | }; | |
| 2923 | } | |
| 2924 | ||
| 2925 | /// The field contains a u32 length followed by that many items for the | |
| 2926 | /// first field, that many items for the second field, etc. | |
| 2927 | pub fn MultiList(comptime ElemArg: type) type { | |
| 2928 | return struct { | |
| 2929 | mal: std.MultiArrayList(Elem), | |
| 2930 | ||
| 2931 | pub const storage: Storage = .multi_list; | |
| 2932 | pub const Elem = ElemArg; | |
| 2933 | }; | |
| 2934 | } | |
| 2935 | ||
| 2936 | /// `UnionArg` is a tagged union with a small integer for the enum tag. | |
| 2937 | /// | |
| 2938 | /// A field in flags determines whether the metadata is present. | |
| 2939 | /// | |
| 2940 | /// The metadata is bit-packed consecutive packed struct which is the | |
| 2941 | /// `UnionArg` enum tag combined with a "last" marker boolean field. | |
| 2942 | /// When "last" is true, the element is the last one, providing | |
| 2943 | /// the length of the list. | |
| 2944 | /// | |
| 2945 | /// Following is each element of the list; each bitcastable to u32. | |
| 2946 | pub fn UnionList( | |
| 2947 | comptime flags_arg: @EnumLiteral(), | |
| 2948 | comptime flag_arg: @EnumLiteral(), | |
| 2949 | comptime UnionArg: type, | |
| 2950 | ) type { | |
| 2951 | return struct { | |
| 2952 | /// When serializing it is UnionArg slice pointer. | |
| 2953 | /// When deserializing it is extra index of first UnionArg element. | |
| 2954 | data: ?*const anyopaque, | |
| 2955 | len: usize, | |
| 2956 | ||
| 2957 | pub const storage: Storage = .union_list; | |
| 2958 | pub const flags = flags_arg; | |
| 2959 | pub const flag = flag_arg; | |
| 2960 | pub const Union = UnionArg; | |
| 2961 | ||
| 2962 | pub const Tag = @typeInfo(Union).@"union".tag_type.?; | |
| 2963 | pub const MetaInt = @Int(.unsigned, @bitSizeOf(Tag) + 1); | |
| 2964 | pub const Meta = packed struct(MetaInt) { | |
| 2965 | tag: Tag, | |
| 2966 | last: bool, | |
| 2967 | }; | |
| 2968 | ||
| 2969 | /// Valid to call only when serializing. | |
| 2970 | pub fn init(s: []const Union) @This() { | |
| 2971 | return .{ .data = s.ptr, .len = s.len }; | |
| 2972 | } | |
| 2973 | ||
| 2974 | /// Valid to call only when deserializing. | |
| 2975 | pub fn slice(this: *const @This(), extra: []const u32) []const u32 { | |
| 2976 | return extra[@intFromPtr(this.data)..][0..this.len]; | |
| 2977 | } | |
| 2978 | ||
| 2979 | /// Valid to call only when deserializing. | |
| 2980 | pub fn get(this: *const @This(), extra: []const u32, i: usize) Union { | |
| 2981 | const elem = slice(this, extra)[i]; | |
| 2982 | return switch (this.tag(extra, i)) { | |
| 2983 | inline else => |comptime_tag| @unionInit(Union, @tagName(comptime_tag), @enumFromInt(elem)), | |
| 2984 | }; | |
| 2985 | } | |
| 2986 | ||
| 2987 | /// Valid to call only when deserializing. | |
| 2988 | pub fn tag(this: *const @This(), extra: []const u32, i: usize) Tag { | |
| 2989 | const start = @intFromPtr(this.data); | |
| 2990 | const meta_start = start - (this.len * @bitSizeOf(Meta) + 31) / 32; | |
| 2991 | return loadBits(u32, extra[meta_start..], i * @bitSizeOf(Meta), Meta).tag; | |
| 2992 | } | |
| 2993 | ||
| 2994 | fn extraLen(len: usize) usize { | |
| 2995 | return len + (len * @bitSizeOf(Meta) + 31) / 32; | |
| 2996 | } | |
| 2997 | }; | |
| 2998 | } | |
| 2999 | ||
| 3000 | pub fn dataLength(buffer: []const u32, i: usize, comptime S: type) usize { | |
| 3001 | var end = i; | |
| 3002 | _ = data(buffer, &end, S); | |
| 3003 | return end - i; | |
| 3004 | } | |
| 3005 | ||
| 3006 | pub fn data(buffer: []const u32, i: *usize, comptime T: type) T { | |
| 3007 | switch (@typeInfo(T)) { | |
| 3008 | .@"struct" => |info| { | |
| 3009 | var result: T = undefined; | |
| 3010 | inline for (info.fields) |field| { | |
| 3011 | @field(result, field.name) = dataField(buffer, i, &result, field.type); | |
| 3012 | } | |
| 3013 | return result; | |
| 3014 | }, | |
| 3015 | .@"union" => |info| { | |
| 3016 | const flags: T.Flags = @bitCast(buffer[i.*]); | |
| 3017 | return switch (flags.tag) { | |
| 3018 | inline else => |comptime_tag| @unionInit( | |
| 3019 | T, | |
| 3020 | @tagName(comptime_tag), | |
| 3021 | data(buffer, i, info.fields[@intFromEnum(comptime_tag)].type), | |
| 3022 | ), | |
| 3023 | }; | |
| 3024 | }, | |
| 3025 | else => comptime unreachable, | |
| 3026 | } | |
| 3027 | } | |
| 3028 | ||
| 3029 | fn dataField(buffer: []const u32, i: *usize, container: anytype, comptime Field: type) Field { | |
| 3030 | switch (@typeInfo(Field)) { | |
| 3031 | .void => return {}, | |
| 3032 | .int => |info| switch (info.bits) { | |
| 3033 | 32 => { | |
| 3034 | defer i.* += 1; | |
| 3035 | return buffer[i.*]; | |
| 3036 | }, | |
| 3037 | 64 => { | |
| 3038 | defer i.* += 2; | |
| 3039 | return @bitCast(buffer[i.*..][0..2].*); | |
| 3040 | }, | |
| 3041 | else => comptime unreachable, | |
| 3042 | }, | |
| 3043 | .@"enum" => { | |
| 3044 | defer i.* += 1; | |
| 3045 | return @enumFromInt(buffer[i.*]); | |
| 3046 | }, | |
| 3047 | .@"struct" => |info| switch (info.layout) { | |
| 3048 | .@"packed" => switch (info.backing_integer.?) { | |
| 3049 | u32 => { | |
| 3050 | defer i.* += 1; | |
| 3051 | return @bitCast(buffer[i.*]); | |
| 3052 | }, | |
| 3053 | u64 => { | |
| 3054 | defer i.* += 2; | |
| 3055 | return @bitCast(buffer[i.*..][0..2].*); | |
| 3056 | }, | |
| 3057 | else => comptime unreachable, | |
| 3058 | }, | |
| 3059 | .auto => switch (Field) { | |
| 3060 | std.Target.Cpu.Feature.Set => { | |
| 3061 | const u32_count = (Field.usize_count * @sizeOf(usize)) / @sizeOf(u32); | |
| 3062 | defer i.* += u32_count; | |
| 3063 | return .{ .ints = @as( | |
| 3064 | *align(@alignOf(u32)) const [Field.usize_count]usize, | |
| 3065 | @ptrCast(buffer[i.*..][0..u32_count]), | |
| 3066 | ).* }; | |
| 3067 | }, | |
| 3068 | else => switch (Field.storage) { | |
| 3069 | .flag_optional => { | |
| 3070 | const flags = @field(container, @tagName(Field.flags)); | |
| 3071 | const flag = @field(flags, @tagName(Field.flag)); | |
| 3072 | return .{ | |
| 3073 | .value = if (flag) dataField(buffer, i, container, Field.Value) else null, | |
| 3074 | }; | |
| 3075 | }, | |
| 3076 | .flag_union => { | |
| 3077 | const flags = @field(container, @tagName(Field.flags)); | |
| 3078 | const tag: Field.Tag = @field(flags, @tagName(Field.flag)); | |
| 3079 | return .{ | |
| 3080 | .u = switch (tag) { | |
| 3081 | inline else => |comptime_tag| @unionInit( | |
| 3082 | Field.Union, | |
| 3083 | @tagName(comptime_tag), | |
| 3084 | dataField( | |
| 3085 | buffer, | |
| 3086 | i, | |
| 3087 | container, | |
| 3088 | @typeInfo(Field.Union).@"union".fields[@intFromEnum(comptime_tag)].type, | |
| 3089 | ), | |
| 3090 | ), | |
| 3091 | }, | |
| 3092 | }; | |
| 3093 | }, | |
| 3094 | .enum_optional => { | |
| 3095 | const flags = @field(container, @tagName(Field.flags)); | |
| 3096 | const tag = @field(flags, @tagName(Field.flag)); | |
| 3097 | const match = tag == Field.tag; | |
| 3098 | return .{ | |
| 3099 | .value = if (match) dataField(buffer, i, container, Field.Value) else null, | |
| 3100 | }; | |
| 3101 | }, | |
| 3102 | .extended => @compileError("unimplemented"), | |
| 3103 | .length_prefixed_list => { | |
| 3104 | const n = @divExact(@sizeOf(Field.Elem), @sizeOf(u32)); | |
| 3105 | const data_start = i.* + 1; | |
| 3106 | const buf_len = buffer[data_start - 1] * n; | |
| 3107 | defer i.* = data_start + buf_len; | |
| 3108 | return .{ .slice = @ptrCast(buffer[data_start..][0..buf_len]) }; | |
| 3109 | }, | |
| 3110 | .flag_length_prefixed_list => { | |
| 3111 | const flags = @field(container, @tagName(Field.flags)); | |
| 3112 | const flag = @field(flags, @tagName(Field.flag)); | |
| 3113 | if (!flag) return .{ .slice = &.{} }; | |
| 3114 | const n = @divExact(@sizeOf(Field.Elem), @sizeOf(u32)); | |
| 3115 | const data_start = i.* + 1; | |
| 3116 | const buf_len = buffer[data_start - 1] * n; | |
| 3117 | defer i.* = data_start + buf_len; | |
| 3118 | return .{ .slice = @ptrCast(buffer[data_start..][0..buf_len]) }; | |
| 3119 | }, | |
| 3120 | .flag_list => { | |
| 3121 | const flags = @field(container, @tagName(Field.flags)); | |
| 3122 | const len: u32 = @field(flags, @tagName(Field.flag)); | |
| 3123 | const data_start = i.*; | |
| 3124 | defer i.* = data_start + len; | |
| 3125 | return .{ .slice = @ptrCast(buffer[data_start..][0..len]) }; | |
| 3126 | }, | |
| 3127 | .multi_list => { | |
| 3128 | const data_start = i.* + 1; | |
| 3129 | const len = buffer[data_start - 1]; | |
| 3130 | defer i.* = data_start + len * @typeInfo(Field.Elem).@"struct".fields.len; | |
| 3131 | return .{ .mal = .{ | |
| 3132 | .bytes = @ptrCast(@constCast(buffer[data_start..][0..len])), | |
| 3133 | .len = len, | |
| 3134 | .capacity = len, | |
| 3135 | } }; | |
| 3136 | }, | |
| 3137 | .union_list => { | |
| 3138 | const flags = @field(container, @tagName(Field.flags)); | |
| 3139 | const flag = @field(flags, @tagName(Field.flag)); | |
| 3140 | if (!flag) return .{ .data = null, .len = 0 }; | |
| 3141 | const meta_start = i.*; | |
| 3142 | const meta_buffer = buffer[meta_start..]; | |
| 3143 | var len: u32 = 0; | |
| 3144 | var bit_offset: usize = 0; | |
| 3145 | while (true) : (bit_offset += @bitSizeOf(Field.Meta)) { | |
| 3146 | const meta = loadBits(u32, meta_buffer, bit_offset, Field.Meta); | |
| 3147 | len += 1; | |
| 3148 | if (meta.last) break; | |
| 3149 | } | |
| 3150 | const end = meta_start + Field.extraLen(len); | |
| 3151 | i.* = end; | |
| 3152 | return .{ .data = @ptrFromInt(end - len), .len = len }; | |
| 3153 | }, | |
| 3154 | }, | |
| 3155 | }, | |
| 3156 | .@"extern" => { | |
| 3157 | const n = @divExact(@sizeOf(Field), @sizeOf(u32)); | |
| 3158 | defer i.* += n; | |
| 3159 | return @bitCast(buffer[i.*..][0..n].*); | |
| 3160 | }, | |
| 3161 | }, | |
| 3162 | else => comptime unreachable, | |
| 3163 | } | |
| 3164 | } | |
| 3165 | ||
| 3166 | /// Returns new end index. | |
| 3167 | fn setExtra(buffer: []u32, index: usize, extra: anytype) usize { | |
| 3168 | const fields = @typeInfo(@TypeOf(extra)).@"struct".fields; | |
| 3169 | var i = index; | |
| 3170 | inline for (fields) |field| { | |
| 3171 | i += setExtraField(buffer, i, field.type, @field(extra, field.name)); | |
| 3172 | } | |
| 3173 | return i; | |
| 3174 | } | |
| 3175 | ||
| 3176 | fn extraFieldLen(field: anytype) usize { | |
| 3177 | const Field = @TypeOf(field); | |
| 3178 | return switch (@typeInfo(Field)) { | |
| 3179 | .void => 0, | |
| 3180 | .int => |info| switch (info.bits) { | |
| 3181 | 32 => 1, | |
| 3182 | 64 => 2, | |
| 3183 | else => comptime unreachable, | |
| 3184 | }, | |
| 3185 | .@"enum" => 1, | |
| 3186 | .@"struct" => |info| switch (info.layout) { | |
| 3187 | .@"packed" => switch (info.backing_integer.?) { | |
| 3188 | u32 => 1, | |
| 3189 | u64 => 2, | |
| 3190 | else => comptime unreachable, | |
| 3191 | }, | |
| 3192 | .auto => switch (Field.storage) { | |
| 3193 | .flag_optional, .enum_optional => (@sizeOf(Field.Value) + 3) / 4, | |
| 3194 | .extended => 1, | |
| 3195 | .length_prefixed_list, | |
| 3196 | .flag_length_prefixed_list, | |
| 3197 | .flag_list, | |
| 3198 | => 1 + @divExact(@sizeOf(Field.Elem), @sizeOf(u32)) * field.slice.len, | |
| 3199 | .multi_list => 1 + field.mal.len * @typeInfo(Field.Elem).@"struct".fields.len, | |
| 3200 | .union_list => Field.extraLen(field.len), | |
| 3201 | .flag_union => switch (field.u) { | |
| 3202 | inline else => |v| extraFieldLen(v), | |
| 3203 | }, | |
| 3204 | }, | |
| 3205 | .@"extern" => @divExact(@sizeOf(Field), @sizeOf(u32)), | |
| 3206 | }, | |
| 3207 | else => @compileError("bad type: " ++ @typeName(Field)), | |
| 3208 | }; | |
| 3209 | } | |
| 3210 | ||
| 3211 | fn extraLen(extra: anytype) usize { | |
| 3212 | const fields = @typeInfo(@TypeOf(extra)).@"struct".fields; | |
| 3213 | var i: usize = 0; | |
| 3214 | inline for (fields) |field| { | |
| 3215 | i += Storage.extraFieldLen(@field(extra, field.name)); | |
| 3216 | } | |
| 3217 | return i; | |
| 3218 | } | |
| 3219 | ||
| 3220 | inline fn setExtraField(buffer: []u32, i: usize, comptime Field: type, value: anytype) usize { | |
| 3221 | switch (@typeInfo(Field)) { | |
| 3222 | .void => return 0, | |
| 3223 | .int => |info| switch (info.bits) { | |
| 3224 | 32 => { | |
| 3225 | buffer[i] = value; | |
| 3226 | return 1; | |
| 3227 | }, | |
| 3228 | 64 => { | |
| 3229 | buffer[i..][0..2].* = @bitCast(value); | |
| 3230 | return 2; | |
| 3231 | }, | |
| 3232 | else => comptime unreachable, | |
| 3233 | }, | |
| 3234 | .@"enum" => { | |
| 3235 | buffer[i] = @intFromEnum(value); | |
| 3236 | return 1; | |
| 3237 | }, | |
| 3238 | .@"struct" => |info| switch (info.layout) { | |
| 3239 | .@"packed" => switch (info.backing_integer.?) { | |
| 3240 | u32 => { | |
| 3241 | buffer[i] = @bitCast(value); | |
| 3242 | return 1; | |
| 3243 | }, | |
| 3244 | u64 => { | |
| 3245 | buffer[i..][0..2].* = @bitCast(value); | |
| 3246 | return 2; | |
| 3247 | }, | |
| 3248 | else => comptime unreachable, | |
| 3249 | }, | |
| 3250 | .auto => switch (Field) { | |
| 3251 | std.Target.Cpu.Feature.Set => { | |
| 3252 | const casted: []const u32 = @ptrCast(&value.ints); | |
| 3253 | @memcpy(buffer[i..][0..casted.len], casted); | |
| 3254 | return casted.len; | |
| 3255 | }, | |
| 3256 | else => switch (Field.storage) { | |
| 3257 | .flag_optional, .enum_optional => { | |
| 3258 | return if (value.value) |v| setExtraField(buffer, i, Field.Value, v) else 0; | |
| 3259 | }, | |
| 3260 | .flag_union => return switch (value.u) { | |
| 3261 | inline else => |x| setExtraField(buffer, i, @TypeOf(x), x), | |
| 3262 | }, | |
| 3263 | .extended => @compileError("unimplemented"), | |
| 3264 | .flag_length_prefixed_list => { | |
| 3265 | const len: u32 = @intCast(value.slice.len); | |
| 3266 | if (len == 0) return 0; // Flag bit hides the length prefix. | |
| 3267 | buffer[i] = len; | |
| 3268 | const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32)); | |
| 3269 | @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice))); | |
| 3270 | return 1 + buf_len; | |
| 3271 | }, | |
| 3272 | .length_prefixed_list => { | |
| 3273 | const len: u32 = @intCast(value.slice.len); | |
| 3274 | buffer[i] = len; | |
| 3275 | const buf_len = len * @divExact(@sizeOf(Field.Elem), @sizeOf(u32)); | |
| 3276 | @memcpy(buffer[i + 1 ..][0..buf_len], @as([]const u32, @ptrCast(value.slice))); | |
| 3277 | return 1 + buf_len; | |
| 3278 | }, | |
| 3279 | .flag_list => { | |
| 3280 | const len: u32 = @intCast(value.slice.len); | |
| 3281 | @memcpy(buffer[i..][0..len], @as([]const u32, @ptrCast(value.slice))); | |
| 3282 | return len; | |
| 3283 | }, | |
| 3284 | .multi_list => { | |
| 3285 | const len: u32 = @intCast(value.mal.len); | |
| 3286 | buffer[i] = len; | |
| 3287 | const fields = @typeInfo(Field.Elem).@"struct".fields; | |
| 3288 | inline for (0..fields.len) |field_i| @memcpy( | |
| 3289 | buffer[i + 1 + field_i * len ..][0..len], | |
| 3290 | @as([]const u32, @ptrCast(value.mal.items(@enumFromInt(field_i)))), | |
| 3291 | ); | |
| 3292 | return 1 + fields.len * len; | |
| 3293 | }, | |
| 3294 | .union_list => { | |
| 3295 | if (value.len == 0) return 0; | |
| 3296 | const Tag = @typeInfo(Field.Union).@"union".tag_type.?; | |
| 3297 | const slice_ptr: [*]const Field.Union = @ptrCast(@alignCast(value.data)); | |
| 3298 | const slice = slice_ptr[0..value.len]; | |
| 3299 | const meta_buffer = buffer[i..][0 .. (slice.len * @bitSizeOf(Field.Meta) + 31) / 32]; | |
| 3300 | for (slice[0 .. slice.len - 1], 0..) |elem, elem_index| { | |
| 3301 | const union_tag: Tag = elem; | |
| 3302 | storeBits(u32, meta_buffer, elem_index * @bitSizeOf(Field.Meta), @as(Field.Meta, .{ | |
| 3303 | .tag = union_tag, | |
| 3304 | .last = false, | |
| 3305 | })); | |
| 3306 | } else { | |
| 3307 | const elem_index = slice.len - 1; | |
| 3308 | const elem = slice[elem_index]; | |
| 3309 | const union_tag: Tag = elem; | |
| 3310 | storeBits(u32, meta_buffer, elem_index * @bitSizeOf(Field.Meta), @as(Field.Meta, .{ | |
| 3311 | .tag = union_tag, | |
| 3312 | .last = true, | |
| 3313 | })); | |
| 3314 | } | |
| 3315 | var total: usize = meta_buffer.len; | |
| 3316 | for (i + meta_buffer.len.., slice) |elem_index, src| switch (src) { | |
| 3317 | inline else => |x| total += setExtraField(buffer, elem_index, @TypeOf(x), x), | |
| 3318 | }; | |
| 3319 | return total; | |
| 3320 | }, | |
| 3321 | }, | |
| 3322 | }, | |
| 3323 | .@"extern" => { | |
| 3324 | const n = @divExact(@sizeOf(Field), @sizeOf(u32)); | |
| 3325 | buffer[i..][0..n].* = @bitCast(value); | |
| 3326 | return n; | |
| 3327 | }, | |
| 3328 | }, | |
| 3329 | else => @compileError("bad field type: " ++ @typeName(Field)), | |
| 3330 | } | |
| 3331 | } | |
| 3332 | }; | |
| 3333 | ||
| 3334 | fn IndexType(comptime T: type) type { | |
| 3335 | return enum(u32) { | |
| 3336 | _, | |
| 3337 | ||
| 3338 | pub fn get(this: @This(), c: *const Configuration) T { | |
| 3339 | return extraData(c, T, @intFromEnum(this)); | |
| 3340 | } | |
| 3341 | }; | |
| 3342 | } | |
| 3343 | ||
| 3344 | pub fn extraData(c: *const Configuration, comptime T: type, index: usize) T { | |
| 3345 | var i: usize = index; | |
| 3346 | return Storage.data(c.extra, &i, T); | |
| 3347 | } | |
| 3348 | ||
| 3349 | pub const LoadFileError = Io.File.Reader.Error || Allocator.Error || error{EndOfStream}; | |
| 3350 | ||
| 3351 | pub fn loadFile(arena: Allocator, io: Io, file: Io.File) LoadFileError!Configuration { | |
| 3352 | var buffer: [2000]u8 = undefined; | |
| 3353 | var fr = file.reader(io, &buffer); | |
| 3354 | return load(arena, &fr.interface) catch |err| switch (err) { | |
| 3355 | error.ReadFailed => return fr.err.?, | |
| 3356 | else => |e| return e, | |
| 3357 | }; | |
| 3358 | } | |
| 3359 | ||
| 3360 | pub const LoadError = Io.Reader.Error || Allocator.Error; | |
| 3361 | ||
| 3362 | pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration { | |
| 3363 | const header = try reader.takeStruct(Header, .native); | |
| 3364 | const result: Configuration = .{ | |
| 3365 | .string_bytes = try arena.alloc(u8, header.string_bytes_len), | |
| 3366 | .steps = try arena.alloc(Step, header.steps_len), | |
| 3367 | .path_deps_sub = try arena.alloc(String, header.path_deps_len), | |
| 3368 | .path_deps_base = try arena.alloc(Path.Base, header.path_deps_len), | |
| 3369 | .unlazy_deps = try arena.alloc(String, header.unlazy_deps_len), | |
| 3370 | .system_integrations = try arena.alloc(SystemIntegration, header.system_integrations_len), | |
| 3371 | .available_options = try arena.alloc(AvailableOption, header.available_options_len), | |
| 3372 | .search_prefixes = try arena.alloc(String, header.search_prefixes_len), | |
| 3373 | .extra = try arena.alloc(u32, header.extra_len), | |
| 3374 | .default_step = header.default_step, | |
| 3375 | .generated_files_len = header.generated_files_len, | |
| 3376 | .poisoned = header.flags.poisoned, | |
| 3377 | }; | |
| 3378 | var vecs = [_][]u8{ | |
| 3379 | result.string_bytes, | |
| 3380 | @ptrCast(result.steps), | |
| 3381 | @ptrCast(result.path_deps_base), | |
| 3382 | @ptrCast(result.path_deps_sub), | |
| 3383 | @ptrCast(result.unlazy_deps), | |
| 3384 | @ptrCast(result.system_integrations), | |
| 3385 | @ptrCast(result.available_options), | |
| 3386 | @ptrCast(result.search_prefixes), | |
| 3387 | @ptrCast(result.extra), | |
| 3388 | }; | |
| 3389 | try reader.readVecAll(&vecs); | |
| 3390 | return result; | |
| 3391 | } | |
| 3392 | ||
| 3393 | pub fn loadBits(comptime Int: type, buffer: []const Int, bit_offset: usize, comptime Result: type) Result { | |
| 3394 | const index = bit_offset / @bitSizeOf(Int); | |
| 3395 | const small_bit_offset = bit_offset % @bitSizeOf(Int); | |
| 3396 | const ResultInt = @Int(.unsigned, @bitSizeOf(Result)); | |
| 3397 | const result: ResultInt = @truncate(buffer[index] >> @intCast(small_bit_offset)); | |
| 3398 | const available_bits = @bitSizeOf(Int) - small_bit_offset; | |
| 3399 | if (available_bits >= @bitSizeOf(ResultInt)) return @bitCast(result); | |
| 3400 | const missing_bits = @bitSizeOf(ResultInt) - available_bits; | |
| 3401 | const upper: ResultInt = @truncate(buffer[index + 1] & ((@as(usize, 1) << @intCast(missing_bits)) - 1)); | |
| 3402 | return @bitCast(result | (upper << @intCast(available_bits))); | |
| 3403 | } | |
| 3404 | ||
| 3405 | pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: anytype) void { | |
| 3406 | const Value = @TypeOf(value); | |
| 3407 | const ValueInt = @Int(.unsigned, @bitSizeOf(Value)); | |
| 3408 | const value_int: ValueInt = @bitCast(value); | |
| 3409 | const index = bit_offset / @bitSizeOf(Int); | |
| 3410 | const small_bit_offset = bit_offset % @bitSizeOf(Int); | |
| 3411 | const available_bits = @bitSizeOf(Int) - small_bit_offset; | |
| 3412 | if (available_bits >= @bitSizeOf(ValueInt)) { | |
| 3413 | buffer[index] &= ~(((@as(Int, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset)); | |
| 3414 | buffer[index] |= @as(Int, value_int) << @intCast(small_bit_offset); | |
| 3415 | } else { | |
| 3416 | const DoubleInt = @Int(.unsigned, @bitSizeOf(Int) * 2); | |
| 3417 | const ptr: *align(@alignOf(Int)) DoubleInt = @ptrCast(buffer[index..][0..2]); | |
| 3418 | ptr.* &= ~(((@as(DoubleInt, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset)); | |
| 3419 | ptr.* |= @as(DoubleInt, value_int) << @intCast(small_bit_offset); | |
| 3420 | } | |
| 3421 | } | |
| 3422 | ||
| 3423 | test "loadBits and storeBits" { | |
| 3424 | var buffer: [2]u32 = .{ | |
| 3425 | 0b01111111000000001111111100000000, | |
| 3426 | 0b11111111000000001111111100000100, | |
| 3427 | }; | |
| 3428 | try std.testing.expectEqual(0b100, loadBits(u32, &buffer, 6, u3)); | |
| 3429 | try std.testing.expectEqual(0b100011, loadBits(u32, &buffer, 29, u6)); | |
| 3430 | ||
| 3431 | storeBits(u32, &buffer, 6, @as(u3, 0b010)); | |
| 3432 | storeBits(u32, &buffer, 29, @as(u6, 0b010010)); | |
| 3433 | ||
| 3434 | try std.testing.expectEqual(0b010, loadBits(u32, &buffer, 6, u3)); | |
| 3435 | try std.testing.expectEqual(0b010010, loadBits(u32, &buffer, 29, u6)); | |
| 3436 | } |
lib/std/Build/Fuzz.zig deleted-597| ... | ... | @@ -1,597 +0,0 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const Io = std.Io; | |
| 3 | const Build = std.Build; | |
| 4 | const Cache = Build.Cache; | |
| 5 | const Step = std.Build.Step; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const fatal = std.process.fatal; | |
| 8 | const Allocator = std.mem.Allocator; | |
| 9 | const log = std.log; | |
| 10 | const Coverage = std.debug.Coverage; | |
| 11 | const abi = Build.abi.fuzz; | |
| 12 | ||
| 13 | const Fuzz = @This(); | |
| 14 | const build_runner = @import("root"); | |
| 15 | ||
| 16 | gpa: Allocator, | |
| 17 | io: Io, | |
| 18 | mode: Mode, | |
| 19 | ||
| 20 | /// Allocated into `gpa`. | |
| 21 | run_steps: []const *Step.Run, | |
| 22 | ||
| 23 | group: Io.Group, | |
| 24 | root_prog_node: std.Progress.Node, | |
| 25 | prog_node: std.Progress.Node, | |
| 26 | ||
| 27 | /// Protects `coverage_files`. | |
| 28 | coverage_mutex: Io.Mutex, | |
| 29 | coverage_files: std.AutoArrayHashMapUnmanaged(u64, CoverageMap), | |
| 30 | ||
| 31 | queue_mutex: Io.Mutex, | |
| 32 | queue_cond: Io.Condition, | |
| 33 | msg_queue: std.ArrayList(Msg), | |
| 34 | ||
| 35 | pub const Mode = union(enum) { | |
| 36 | forever: struct { ws: *Build.WebServer }, | |
| 37 | limit: Limited, | |
| 38 | ||
| 39 | pub const Limited = struct { | |
| 40 | amount: u64, | |
| 41 | }; | |
| 42 | }; | |
| 43 | ||
| 44 | const Msg = union(enum) { | |
| 45 | coverage: struct { | |
| 46 | id: u64, | |
| 47 | cumulative: struct { | |
| 48 | runs: u64, | |
| 49 | unique: u64, | |
| 50 | coverage: u64, | |
| 51 | }, | |
| 52 | run: *Step.Run, | |
| 53 | }, | |
| 54 | entry_point: struct { | |
| 55 | coverage_id: u64, | |
| 56 | addr: u64, | |
| 57 | }, | |
| 58 | }; | |
| 59 | ||
| 60 | const CoverageMap = struct { | |
| 61 | mapped_memory: []align(std.heap.page_size_min) const u8, | |
| 62 | coverage: Coverage, | |
| 63 | source_locations: []Coverage.SourceLocation, | |
| 64 | /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested. | |
| 65 | entry_points: std.ArrayList(u32), | |
| 66 | start_timestamp: i64, | |
| 67 | start_n_runs: u64, | |
| 68 | ||
| 69 | fn deinit(cm: *CoverageMap, gpa: Allocator) void { | |
| 70 | std.posix.munmap(cm.mapped_memory); | |
| 71 | cm.coverage.deinit(gpa); | |
| 72 | cm.* = undefined; | |
| 73 | } | |
| 74 | }; | |
| 75 | ||
| 76 | pub fn init( | |
| 77 | gpa: Allocator, | |
| 78 | io: Io, | |
| 79 | all_steps: []const *Build.Step, | |
| 80 | root_prog_node: std.Progress.Node, | |
| 81 | mode: Mode, | |
| 82 | ) error{ OutOfMemory, Canceled }!Fuzz { | |
| 83 | const run_steps: []const *Step.Run = steps: { | |
| 84 | var steps: std.ArrayList(*Step.Run) = .empty; | |
| 85 | defer steps.deinit(gpa); | |
| 86 | const rebuild_node = root_prog_node.start("Rebuilding Unit Tests", 0); | |
| 87 | defer rebuild_node.end(); | |
| 88 | var rebuild_group: Io.Group = .init; | |
| 89 | defer rebuild_group.cancel(io); | |
| 90 | ||
| 91 | for (all_steps) |step| { | |
| 92 | const run = step.cast(Step.Run) orelse continue; | |
| 93 | if (run.producer == null) continue; | |
| 94 | if (run.fuzz_tests.items.len == 0) continue; | |
| 95 | try steps.append(gpa, run); | |
| 96 | rebuild_group.async(io, rebuildTestsWorkerRun, .{ run, gpa, rebuild_node }); | |
| 97 | } | |
| 98 | ||
| 99 | if (steps.items.len == 0) fatal("no fuzz tests found", .{}); | |
| 100 | rebuild_node.setEstimatedTotalItems(steps.items.len); | |
| 101 | const run_steps = try gpa.dupe(*Step.Run, steps.items); | |
| 102 | try rebuild_group.await(io); | |
| 103 | break :steps run_steps; | |
| 104 | }; | |
| 105 | errdefer gpa.free(run_steps); | |
| 106 | ||
| 107 | for (run_steps) |run| { | |
| 108 | assert(run.fuzz_tests.items.len > 0); | |
| 109 | if (run.rebuilt_executable == null) | |
| 110 | fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{}); | |
| 111 | } | |
| 112 | ||
| 113 | return .{ | |
| 114 | .gpa = gpa, | |
| 115 | .io = io, | |
| 116 | .mode = mode, | |
| 117 | .run_steps = run_steps, | |
| 118 | .group = .init, | |
| 119 | .root_prog_node = root_prog_node, | |
| 120 | .prog_node = .none, | |
| 121 | .coverage_files = .empty, | |
| 122 | .coverage_mutex = .init, | |
| 123 | .queue_mutex = .init, | |
| 124 | .queue_cond = .init, | |
| 125 | .msg_queue = .empty, | |
| 126 | }; | |
| 127 | } | |
| 128 | ||
| 129 | pub fn start(fuzz: *Fuzz) void { | |
| 130 | const io = fuzz.io; | |
| 131 | fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", 0); | |
| 132 | ||
| 133 | if (fuzz.mode == .forever) { | |
| 134 | // For polling messages and sending updates to subscribers. | |
| 135 | fuzz.group.concurrent(io, coverageRun, .{fuzz}) catch |err| | |
| 136 | fatal("unable to spawn coverage task: {t}", .{err}); | |
| 137 | } | |
| 138 | ||
| 139 | for (fuzz.run_steps) |run| { | |
| 140 | assert(run.rebuilt_executable != null); | |
| 141 | fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run }); | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | pub fn deinit(fuzz: *Fuzz) void { | |
| 146 | const io = fuzz.io; | |
| 147 | fuzz.group.cancel(io); | |
| 148 | fuzz.prog_node.end(); | |
| 149 | fuzz.gpa.free(fuzz.run_steps); | |
| 150 | } | |
| 151 | ||
| 152 | fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) void { | |
| 153 | rebuildTestsWorkerRunFallible(run, gpa, parent_prog_node) catch |err| { | |
| 154 | const compile = run.producer.?; | |
| 155 | log.err("step '{s}': failed to rebuild in fuzz mode: {t}", .{ compile.step.name, err }); | |
| 156 | }; | |
| 157 | } | |
| 158 | ||
| 159 | fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_node: std.Progress.Node) !void { | |
| 160 | const graph = run.step.owner.graph; | |
| 161 | const io = graph.io; | |
| 162 | const compile = run.producer.?; | |
| 163 | const prog_node = parent_prog_node.start(compile.step.name, 0); | |
| 164 | defer prog_node.end(); | |
| 165 | ||
| 166 | const result = compile.rebuildInFuzzMode(gpa, prog_node); | |
| 167 | ||
| 168 | const show_compile_errors = compile.step.result_error_bundle.errorMessageCount() > 0; | |
| 169 | const show_error_msgs = compile.step.result_error_msgs.items.len > 0; | |
| 170 | const show_stderr = compile.step.result_stderr.len > 0; | |
| 171 | ||
| 172 | if (show_error_msgs or show_compile_errors or show_stderr) { | |
| 173 | var buf: [256]u8 = undefined; | |
| 174 | const stderr = try io.lockStderr(&buf, graph.stderr_mode); | |
| 175 | defer io.unlockStderr(); | |
| 176 | build_runner.printErrorMessages(gpa, &compile.step, .{}, stderr.terminal(), .verbose, .indent) catch {}; | |
| 177 | } | |
| 178 | ||
| 179 | const rebuilt_bin_path = result catch |err| switch (err) { | |
| 180 | error.MakeFailed => return, | |
| 181 | else => |other| return other, | |
| 182 | }; | |
| 183 | run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename); | |
| 184 | } | |
| 185 | ||
| 186 | fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run) void { | |
| 187 | const owner = run.step.owner; | |
| 188 | const gpa = owner.allocator; | |
| 189 | const graph = owner.graph; | |
| 190 | const io = graph.io; | |
| 191 | ||
| 192 | run.rerunInFuzzMode(fuzz, fuzz.prog_node) catch |err| switch (err) { | |
| 193 | error.MakeFailed => { | |
| 194 | var buf: [256]u8 = undefined; | |
| 195 | const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) { | |
| 196 | error.Canceled => return, | |
| 197 | }; | |
| 198 | defer io.unlockStderr(); | |
| 199 | build_runner.printErrorMessages(gpa, &run.step, .{}, stderr.terminal(), .verbose, .indent) catch {}; | |
| 200 | return; | |
| 201 | }, | |
| 202 | else => { | |
| 203 | log.err("step '{s}': failed to rerun in fuzz mode: {t}", .{ run.step.name, err }); | |
| 204 | return; | |
| 205 | }, | |
| 206 | }; | |
| 207 | } | |
| 208 | ||
| 209 | pub fn serveSourcesTar(fuzz: *Fuzz, req: *std.http.Server.Request) !void { | |
| 210 | assert(fuzz.mode == .forever); | |
| 211 | ||
| 212 | var arena_state: std.heap.ArenaAllocator = .init(fuzz.gpa); | |
| 213 | defer arena_state.deinit(); | |
| 214 | const arena = arena_state.allocator(); | |
| 215 | ||
| 216 | const DedupTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false); | |
| 217 | var dedup_table: DedupTable = .empty; | |
| 218 | defer dedup_table.deinit(fuzz.gpa); | |
| 219 | ||
| 220 | for (fuzz.run_steps) |run_step| { | |
| 221 | const compile_inputs = run_step.producer.?.step.inputs.table; | |
| 222 | for (compile_inputs.keys(), compile_inputs.values()) |dir_path, *file_list| { | |
| 223 | try dedup_table.ensureUnusedCapacity(fuzz.gpa, file_list.items.len); | |
| 224 | for (file_list.items) |sub_path| { | |
| 225 | if (!std.mem.endsWith(u8, sub_path, ".zig")) continue; | |
| 226 | const joined_path = try dir_path.join(arena, sub_path); | |
| 227 | dedup_table.putAssumeCapacity(joined_path, {}); | |
| 228 | } | |
| 229 | } | |
| 230 | } | |
| 231 | ||
| 232 | const deduped_paths = dedup_table.keys(); | |
| 233 | const SortContext = struct { | |
| 234 | pub fn lessThan(this: @This(), lhs: Build.Cache.Path, rhs: Build.Cache.Path) bool { | |
| 235 | _ = this; | |
| 236 | return switch (std.mem.order(u8, lhs.root_dir.path orelse ".", rhs.root_dir.path orelse ".")) { | |
| 237 | .lt => true, | |
| 238 | .gt => false, | |
| 239 | .eq => std.mem.lessThan(u8, lhs.sub_path, rhs.sub_path), | |
| 240 | }; | |
| 241 | } | |
| 242 | }; | |
| 243 | std.mem.sortUnstable(Build.Cache.Path, deduped_paths, SortContext{}, SortContext.lessThan); | |
| 244 | return fuzz.mode.forever.ws.serveTarFile(req, deduped_paths); | |
| 245 | } | |
| 246 | ||
| 247 | pub const Previous = struct { | |
| 248 | unique_runs: usize, | |
| 249 | entry_points: usize, | |
| 250 | sent_source_index: bool, | |
| 251 | pub const init: Previous = .{ | |
| 252 | .unique_runs = 0, | |
| 253 | .entry_points = 0, | |
| 254 | .sent_source_index = false, | |
| 255 | }; | |
| 256 | }; | |
| 257 | pub fn sendUpdate( | |
| 258 | fuzz: *Fuzz, | |
| 259 | socket: *std.http.Server.WebSocket, | |
| 260 | prev: *Previous, | |
| 261 | ) !void { | |
| 262 | const io = fuzz.io; | |
| 263 | ||
| 264 | try fuzz.coverage_mutex.lock(io); | |
| 265 | defer fuzz.coverage_mutex.unlock(io); | |
| 266 | ||
| 267 | const coverage_maps = fuzz.coverage_files.values(); | |
| 268 | if (coverage_maps.len == 0) return; | |
| 269 | // TODO: handle multiple fuzz steps in the WebSocket packets | |
| 270 | const coverage_map = &coverage_maps[0]; | |
| 271 | const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 272 | // TODO: this isn't sound! We need to do volatile reads of these bits rather than handing the | |
| 273 | // buffer off to the kernel, because we might race with the fuzzer process[es]. This brings the | |
| 274 | // whole mmap strategy into question. Incidentally, I wonder if post-writergate we could pass | |
| 275 | // this data straight to the socket with sendfile... | |
| 276 | const seen_pcs = cov_header.seenBits(); | |
| 277 | const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic); | |
| 278 | const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic); | |
| 279 | { | |
| 280 | if (!prev.sent_source_index) { | |
| 281 | prev.sent_source_index = true; | |
| 282 | // We need to send initial context. | |
| 283 | const header: abi.SourceIndexHeader = .{ | |
| 284 | .directories_len = @intCast(coverage_map.coverage.directories.entries.len), | |
| 285 | .files_len = @intCast(coverage_map.coverage.files.entries.len), | |
| 286 | .source_locations_len = @intCast(coverage_map.source_locations.len), | |
| 287 | .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len), | |
| 288 | .start_timestamp = coverage_map.start_timestamp, | |
| 289 | .start_n_runs = coverage_map.start_n_runs, | |
| 290 | }; | |
| 291 | var iovecs: [5][]const u8 = .{ | |
| 292 | @ptrCast(&header), | |
| 293 | @ptrCast(coverage_map.coverage.directories.keys()), | |
| 294 | @ptrCast(coverage_map.coverage.files.keys()), | |
| 295 | @ptrCast(coverage_map.source_locations), | |
| 296 | coverage_map.coverage.string_bytes.items, | |
| 297 | }; | |
| 298 | try socket.writeMessageVec(&iovecs, .binary); | |
| 299 | } | |
| 300 | ||
| 301 | const header: abi.CoverageUpdateHeader = .{ | |
| 302 | .n_runs = n_runs, | |
| 303 | .unique_runs = unique_runs, | |
| 304 | }; | |
| 305 | var iovecs: [2][]const u8 = .{ | |
| 306 | @ptrCast(&header), | |
| 307 | @ptrCast(seen_pcs), | |
| 308 | }; | |
| 309 | try socket.writeMessageVec(&iovecs, .binary); | |
| 310 | ||
| 311 | prev.unique_runs = unique_runs; | |
| 312 | } | |
| 313 | ||
| 314 | if (prev.entry_points != coverage_map.entry_points.items.len) { | |
| 315 | const header: abi.EntryPointHeader = .init(@intCast(coverage_map.entry_points.items.len)); | |
| 316 | var iovecs: [2][]const u8 = .{ | |
| 317 | @ptrCast(&header), | |
| 318 | @ptrCast(coverage_map.entry_points.items), | |
| 319 | }; | |
| 320 | try socket.writeMessageVec(&iovecs, .binary); | |
| 321 | ||
| 322 | prev.entry_points = coverage_map.entry_points.items.len; | |
| 323 | } | |
| 324 | } | |
| 325 | ||
| 326 | fn coverageRun(fuzz: *Fuzz) void { | |
| 327 | coverageRunCancelable(fuzz) catch |err| switch (err) { | |
| 328 | error.Canceled => return, | |
| 329 | }; | |
| 330 | } | |
| 331 | ||
| 332 | fn coverageRunCancelable(fuzz: *Fuzz) Io.Cancelable!void { | |
| 333 | const io = fuzz.io; | |
| 334 | ||
| 335 | try fuzz.queue_mutex.lock(io); | |
| 336 | defer fuzz.queue_mutex.unlock(io); | |
| 337 | ||
| 338 | while (true) { | |
| 339 | try fuzz.queue_cond.wait(io, &fuzz.queue_mutex); | |
| 340 | for (fuzz.msg_queue.items) |msg| switch (msg) { | |
| 341 | .coverage => |coverage| prepareTables(fuzz, coverage.run, coverage.id) catch |err| switch (err) { | |
| 342 | error.AlreadyReported => continue, | |
| 343 | error.Canceled => return, | |
| 344 | else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}), | |
| 345 | }, | |
| 346 | .entry_point => |entry_point| addEntryPoint(fuzz, entry_point.coverage_id, entry_point.addr) catch |err| switch (err) { | |
| 347 | error.AlreadyReported => continue, | |
| 348 | error.Canceled => return, | |
| 349 | else => |e| log.err("failed to prepare code coverage tables: {t}", .{e}), | |
| 350 | }, | |
| 351 | }; | |
| 352 | fuzz.msg_queue.clearRetainingCapacity(); | |
| 353 | } | |
| 354 | } | |
| 355 | fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported, Canceled }!void { | |
| 356 | assert(fuzz.mode == .forever); | |
| 357 | const ws = fuzz.mode.forever.ws; | |
| 358 | const gpa = fuzz.gpa; | |
| 359 | const io = fuzz.io; | |
| 360 | ||
| 361 | try fuzz.coverage_mutex.lock(io); | |
| 362 | defer fuzz.coverage_mutex.unlock(io); | |
| 363 | ||
| 364 | const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id); | |
| 365 | if (gop.found_existing) { | |
| 366 | // We are fuzzing the same executable with multiple threads. | |
| 367 | // Perhaps the same unit test; perhaps a different one. In any | |
| 368 | // case, since the coverage file is the same, we only have to | |
| 369 | // notice changes to that one file in order to learn coverage for | |
| 370 | // this particular executable. | |
| 371 | return; | |
| 372 | } | |
| 373 | errdefer _ = fuzz.coverage_files.pop(); | |
| 374 | ||
| 375 | gop.value_ptr.* = .{ | |
| 376 | .coverage = std.debug.Coverage.init, | |
| 377 | .mapped_memory = undefined, // populated below | |
| 378 | .source_locations = undefined, // populated below | |
| 379 | .entry_points = .empty, | |
| 380 | .start_timestamp = ws.now(), | |
| 381 | .start_n_runs = undefined, // populated below | |
| 382 | }; | |
| 383 | errdefer gop.value_ptr.coverage.deinit(gpa); | |
| 384 | ||
| 385 | const rebuilt_exe_path = run_step.rebuilt_executable.?; | |
| 386 | const target = run_step.producer.?.rootModuleTarget(); | |
| 387 | var debug_info = std.debug.Info.load( | |
| 388 | gpa, | |
| 389 | io, | |
| 390 | rebuilt_exe_path, | |
| 391 | &gop.value_ptr.coverage, | |
| 392 | target.ofmt, | |
| 393 | target.cpu.arch, | |
| 394 | ) catch |err| { | |
| 395 | log.err("step '{s}': failed to load debug information for '{f}': {t}", .{ | |
| 396 | run_step.step.name, rebuilt_exe_path, err, | |
| 397 | }); | |
| 398 | return error.AlreadyReported; | |
| 399 | }; | |
| 400 | defer debug_info.deinit(gpa); | |
| 401 | ||
| 402 | const coverage_file_path: Build.Cache.Path = .{ | |
| 403 | .root_dir = run_step.step.owner.cache_root, | |
| 404 | .sub_path = "v/" ++ std.fmt.hex(coverage_id), | |
| 405 | }; | |
| 406 | var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| { | |
| 407 | log.err("step '{s}': failed to load coverage file '{f}': {t}", .{ | |
| 408 | run_step.step.name, coverage_file_path, err, | |
| 409 | }); | |
| 410 | return error.AlreadyReported; | |
| 411 | }; | |
| 412 | defer coverage_file.close(io); | |
| 413 | ||
| 414 | const file_size = coverage_file.length(io) catch |err| { | |
| 415 | log.err("unable to check len of coverage file '{f}': {t}", .{ coverage_file_path, err }); | |
| 416 | return error.AlreadyReported; | |
| 417 | }; | |
| 418 | ||
| 419 | const mapped_memory = std.posix.mmap( | |
| 420 | null, | |
| 421 | file_size, | |
| 422 | .{ .READ = true }, | |
| 423 | .{ .TYPE = .SHARED }, | |
| 424 | coverage_file.handle, | |
| 425 | 0, | |
| 426 | ) catch |err| { | |
| 427 | log.err("failed to map coverage file '{f}': {t}", .{ coverage_file_path, err }); | |
| 428 | return error.AlreadyReported; | |
| 429 | }; | |
| 430 | gop.value_ptr.mapped_memory = mapped_memory; | |
| 431 | ||
| 432 | const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 433 | const pcs = header.pcAddrs(); | |
| 434 | const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len); | |
| 435 | errdefer gpa.free(source_locations); | |
| 436 | ||
| 437 | // Unfortunately the PCs array that LLVM gives us from the 8-bit PC | |
| 438 | // counters feature is not sorted. | |
| 439 | var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .empty; | |
| 440 | defer sorted_pcs.deinit(gpa); | |
| 441 | try sorted_pcs.resize(gpa, pcs.len); | |
| 442 | @memcpy(sorted_pcs.items(.pc), pcs); | |
| 443 | for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i); | |
| 444 | sorted_pcs.sortUnstable(struct { | |
| 445 | addrs: []const u64, | |
| 446 | ||
| 447 | pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool { | |
| 448 | return ctx.addrs[a_index] < ctx.addrs[b_index]; | |
| 449 | } | |
| 450 | }{ .addrs = sorted_pcs.items(.pc) }); | |
| 451 | ||
| 452 | debug_info.resolveAddresses(gpa, io, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| { | |
| 453 | log.err("failed to resolve addresses to source locations: {t}", .{err}); | |
| 454 | return error.AlreadyReported; | |
| 455 | }; | |
| 456 | ||
| 457 | for (sorted_pcs.items(.index), sorted_pcs.items(.sl)) |i, sl| source_locations[i] = sl; | |
| 458 | gop.value_ptr.source_locations = source_locations; | |
| 459 | gop.value_ptr.start_n_runs = header.n_runs; | |
| 460 | ||
| 461 | ws.notifyUpdate(); | |
| 462 | } | |
| 463 | ||
| 464 | fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReported, OutOfMemory, Canceled }!void { | |
| 465 | const io = fuzz.io; | |
| 466 | ||
| 467 | try fuzz.coverage_mutex.lock(io); | |
| 468 | defer fuzz.coverage_mutex.unlock(io); | |
| 469 | ||
| 470 | const coverage_map = fuzz.coverage_files.getPtr(coverage_id).?; | |
| 471 | const header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); | |
| 472 | const pcs = header.pcAddrs(); | |
| 473 | ||
| 474 | // Since this pcs list is unsorted, we must linear scan for the best index. | |
| 475 | const index = i: { | |
| 476 | var best: usize = 0; | |
| 477 | for (pcs[1..], 1..) |elem_addr, i| { | |
| 478 | if (elem_addr == addr) break :i i; | |
| 479 | if (elem_addr > addr) continue; | |
| 480 | if (elem_addr > pcs[best]) best = i; | |
| 481 | } | |
| 482 | break :i best; | |
| 483 | }; | |
| 484 | if (index >= pcs.len) { | |
| 485 | log.err("unable to find unit test entry address 0x{x} in source locations (range: 0x{x} to 0x{x})", .{ | |
| 486 | addr, pcs[0], pcs[pcs.len - 1], | |
| 487 | }); | |
| 488 | return error.AlreadyReported; | |
| 489 | } | |
| 490 | if (false) { | |
| 491 | const sl = coverage_map.source_locations[index]; | |
| 492 | const file_name = coverage_map.coverage.stringAt(coverage_map.coverage.fileAt(sl.file).basename); | |
| 493 | if (pcs.len == 1) { | |
| 494 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 (final)", .{ | |
| 495 | addr, file_name, sl.line, sl.column, | |
| 496 | }); | |
| 497 | } else if (index == 0) { | |
| 498 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index 0 before {x}", .{ | |
| 499 | addr, file_name, sl.line, sl.column, pcs[index + 1], | |
| 500 | }); | |
| 501 | } else if (index == pcs.len - 1) { | |
| 502 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} (final) after {x}", .{ | |
| 503 | addr, file_name, sl.line, sl.column, index, pcs[index - 1], | |
| 504 | }); | |
| 505 | } else { | |
| 506 | log.debug("server found entry point for 0x{x} at {s}:{d}:{d} - index {d} between {x} and {x}", .{ | |
| 507 | addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1], | |
| 508 | }); | |
| 509 | } | |
| 510 | } | |
| 511 | try coverage_map.entry_points.append(fuzz.gpa, @intCast(index)); | |
| 512 | } | |
| 513 | ||
| 514 | pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void { | |
| 515 | assert(fuzz.mode == .limit); | |
| 516 | const io = fuzz.io; | |
| 517 | ||
| 518 | try fuzz.group.await(io); | |
| 519 | fuzz.group = .init; | |
| 520 | ||
| 521 | std.debug.print("======= FUZZING REPORT =======\n", .{}); | |
| 522 | for (fuzz.msg_queue.items) |msg| { | |
| 523 | if (msg != .coverage) continue; | |
| 524 | ||
| 525 | const cov = msg.coverage; | |
| 526 | const coverage_file_path: std.Build.Cache.Path = .{ | |
| 527 | .root_dir = cov.run.step.owner.cache_root, | |
| 528 | .sub_path = "v/" ++ std.fmt.hex(cov.id), | |
| 529 | }; | |
| 530 | var coverage_file = coverage_file_path.root_dir.handle.openFile(io, coverage_file_path.sub_path, .{}) catch |err| { | |
| 531 | fatal("step '{s}': failed to load coverage file '{f}': {t}", .{ | |
| 532 | cov.run.step.name, coverage_file_path, err, | |
| 533 | }); | |
| 534 | }; | |
| 535 | defer coverage_file.close(io); | |
| 536 | ||
| 537 | const fuzz_abi = std.Build.abi.fuzz; | |
| 538 | var rbuf: [0x1000]u8 = undefined; | |
| 539 | var r = coverage_file.reader(io, &rbuf); | |
| 540 | ||
| 541 | var header: fuzz_abi.SeenPcsHeader = undefined; | |
| 542 | r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| { | |
| 543 | fatal("step '{s}': failed to read from coverage file '{f}': {t}", .{ | |
| 544 | cov.run.step.name, coverage_file_path, err, | |
| 545 | }); | |
| 546 | }; | |
| 547 | ||
| 548 | if (header.pcs_len == 0) { | |
| 549 | fatal("step '{s}': corrupted coverage file '{f}': pcs_len was zero", .{ | |
| 550 | cov.run.step.name, coverage_file_path, | |
| 551 | }); | |
| 552 | } | |
| 553 | ||
| 554 | var seen_count: usize = 0; | |
| 555 | const chunk_count = fuzz_abi.SeenPcsHeader.seenElemsLen(header.pcs_len); | |
| 556 | for (0..chunk_count) |_| { | |
| 557 | const seen = r.interface.takeInt(usize, .little) catch |err| { | |
| 558 | fatal("step '{s}': failed to read from coverage file '{f}': {t}", .{ | |
| 559 | cov.run.step.name, coverage_file_path, err, | |
| 560 | }); | |
| 561 | }; | |
| 562 | seen_count += @popCount(seen); | |
| 563 | } | |
| 564 | ||
| 565 | const seen_f: f64 = @floatFromInt(seen_count); | |
| 566 | const total_f: f64 = @floatFromInt(header.pcs_len); | |
| 567 | const ratio = seen_f / total_f; | |
| 568 | std.debug.print( | |
| 569 | \\Step: {s} | |
| 570 | \\Fuzz test: "{s}" ({x}) | |
| 571 | \\Runs: {} -> {} | |
| 572 | \\Unique runs: {} -> {} | |
| 573 | \\Coverage: {}/{} -> {}/{} ({:.02}%) | |
| 574 | \\ | |
| 575 | , .{ | |
| 576 | cov.run.step.name, | |
| 577 | cov.run.fuzz_tests.items[0], | |
| 578 | cov.id, | |
| 579 | cov.cumulative.runs, | |
| 580 | header.n_runs, | |
| 581 | cov.cumulative.unique, | |
| 582 | header.unique_runs, | |
| 583 | cov.cumulative.coverage, | |
| 584 | header.pcs_len, | |
| 585 | seen_count, | |
| 586 | header.pcs_len, | |
| 587 | ratio * 100, | |
| 588 | }); | |
| 589 | ||
| 590 | std.debug.print("------------------------------\n", .{}); | |
| 591 | } | |
| 592 | std.debug.print( | |
| 593 | \\Values are accumulated across multiple runs when preserving the cache. | |
| 594 | \\============================== | |
| 595 | \\ | |
| 596 | , .{}); | |
| 597 | } |
lib/std/Build/Module.zig+99-225| ... | ... | @@ -1,3 +1,11 @@ |
| 1 | const Module = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const assert = std.debug.assert; | |
| 5 | const LazyPath = std.Build.LazyPath; | |
| 6 | const Step = std.Build.Step; | |
| 7 | const ArrayList = std.ArrayList; | |
| 8 | ||
| 1 | 9 | /// The one responsible for creating this module. |
| 2 | 10 | owner: *std.Build, |
| 3 | 11 | root_source_file: ?LazyPath, |
| ... | ... | @@ -65,18 +73,8 @@ pub const SystemLib = struct { |
| 65 | 73 | preferred_link_mode: std.builtin.LinkMode, |
| 66 | 74 | search_strategy: SystemLib.SearchStrategy, |
| 67 | 75 | |
| 68 | pub const UsePkgConfig = enum { | |
| 69 | /// Don't use pkg-config, just pass -lfoo where foo is name. | |
| 70 | no, | |
| 71 | /// Try to get information on how to link the library from pkg-config. | |
| 72 | /// If that fails, fall back to passing -lfoo where foo is name. | |
| 73 | yes, | |
| 74 | /// Try to get information on how to link the library from pkg-config. | |
| 75 | /// If that fails, error out. | |
| 76 | force, | |
| 77 | }; | |
| 78 | ||
| 79 | pub const SearchStrategy = enum { paths_first, mode_first, no_fallback }; | |
| 76 | pub const UsePkgConfig = std.Build.Configuration.SystemLib.UsePkgConfig; | |
| 77 | pub const SearchStrategy = std.Build.Configuration.SystemLib.SearchStrategy; | |
| 80 | 78 | }; |
| 81 | 79 | |
| 82 | 80 | pub const CSourceLanguage = enum { |
| ... | ... | @@ -91,7 +89,8 @@ pub const CSourceLanguage = enum { |
| 91 | 89 | /// Assembly with the C preprocessor |
| 92 | 90 | assembly_with_preprocessor, |
| 93 | 91 | |
| 94 | pub fn internalIdentifier(self: CSourceLanguage) []const u8 { | |
| 92 | /// The value passed to "-x" CLI flag of Clang. | |
| 93 | pub fn clangIdentifier(self: CSourceLanguage) [:0]const u8 { | |
| 95 | 94 | return switch (self) { |
| 96 | 95 | .c => "c", |
| 97 | 96 | .cpp => "c++", |
| ... | ... | @@ -119,10 +118,10 @@ pub const CSourceFile = struct { |
| 119 | 118 | /// By default, determines language of each file individually based on its file extension |
| 120 | 119 | language: ?CSourceLanguage = null, |
| 121 | 120 | |
| 122 | pub fn dupe(file: CSourceFile, b: *std.Build) CSourceFile { | |
| 121 | pub fn dupe(file: CSourceFile, graph: *const std.Build.Graph) CSourceFile { | |
| 123 | 122 | return .{ |
| 124 | .file = file.file.dupe(b), | |
| 125 | .flags = b.dupeStrings(file.flags), | |
| 123 | .file = file.file.dupe(graph), | |
| 124 | .flags = graph.dupeStrings(file.flags), | |
| 126 | 125 | .language = file.language, |
| 127 | 126 | }; |
| 128 | 127 | } |
| ... | ... | @@ -146,12 +145,13 @@ pub const RcSourceFile = struct { |
| 146 | 145 | /// as `/I <resolved path>`. |
| 147 | 146 | include_paths: []const LazyPath = &.{}, |
| 148 | 147 | |
| 149 | pub fn dupe(file: RcSourceFile, b: *std.Build) RcSourceFile { | |
| 150 | const include_paths = b.allocator.alloc(LazyPath, file.include_paths.len) catch @panic("OOM"); | |
| 151 | for (include_paths, file.include_paths) |*dest, lazy_path| dest.* = lazy_path.dupe(b); | |
| 148 | pub fn dupe(file: RcSourceFile, graph: *const std.Build.Graph) RcSourceFile { | |
| 149 | const arena = graph.arena; | |
| 150 | const include_paths = arena.alloc(LazyPath, file.include_paths.len) catch @panic("OOM"); | |
| 151 | for (include_paths, file.include_paths) |*dest, lazy_path| dest.* = lazy_path.dupe(graph); | |
| 152 | 152 | return .{ |
| 153 | .file = file.file.dupe(b), | |
| 154 | .flags = b.dupeStrings(file.flags), | |
| 153 | .file = file.file.dupe(graph), | |
| 154 | .flags = graph.dupeStrings(file.flags), | |
| 155 | 155 | .include_paths = include_paths, |
| 156 | 156 | }; |
| 157 | 157 | } |
| ... | ... | @@ -166,33 +166,6 @@ pub const IncludeDir = union(enum) { |
| 166 | 166 | other_step: *Step.Compile, |
| 167 | 167 | config_header_step: *Step.ConfigHeader, |
| 168 | 168 | embed_path: LazyPath, |
| 169 | ||
| 170 | pub fn appendZigProcessFlags( | |
| 171 | include_dir: IncludeDir, | |
| 172 | b: *std.Build, | |
| 173 | zig_args: *std.array_list.Managed([]const u8), | |
| 174 | asking_step: ?*Step, | |
| 175 | ) !void { | |
| 176 | const flag: []const u8, const lazy_path: LazyPath = switch (include_dir) { | |
| 177 | // zig fmt: off | |
| 178 | .path => |lp| .{ "-I", lp }, | |
| 179 | .path_system => |lp| .{ "-isystem", lp }, | |
| 180 | .path_after => |lp| .{ "-idirafter", lp }, | |
| 181 | .framework_path => |lp| .{ "-F", lp }, | |
| 182 | .framework_path_system => |lp| .{ "-iframework", lp }, | |
| 183 | .config_header_step => |ch| .{ "-I", ch.getOutputDir() }, | |
| 184 | .other_step => |comp| .{ "-I", comp.installed_headers_include_tree.?.getDirectory() }, | |
| 185 | // zig fmt: on | |
| 186 | .embed_path => |lazy_path| { | |
| 187 | // Special case: this is a single arg. | |
| 188 | const resolved = lazy_path.getPath3(b, asking_step); | |
| 189 | const arg = b.fmt("--embed-dir={f}", .{resolved}); | |
| 190 | return zig_args.append(arg); | |
| 191 | }, | |
| 192 | }; | |
| 193 | const resolved_str = try lazy_path.getPath3(b, asking_step).toString(b.graph.arena); | |
| 194 | return zig_args.appendSlice(&.{ flag, resolved_str }); | |
| 195 | } | |
| 196 | 169 | }; |
| 197 | 170 | |
| 198 | 171 | pub const LinkFrameworkOptions = struct { |
| ... | ... | @@ -268,13 +241,14 @@ pub fn init( |
| 268 | 241 | owner: *std.Build, |
| 269 | 242 | value: union(enum) { options: CreateOptions, existing: *const Module }, |
| 270 | 243 | ) void { |
| 271 | const allocator = owner.allocator; | |
| 244 | const graph = owner.graph; | |
| 245 | const arena = graph.arena; | |
| 272 | 246 | |
| 273 | 247 | switch (value) { |
| 274 | 248 | .options => |options| { |
| 275 | 249 | m.* = .{ |
| 276 | 250 | .owner = owner, |
| 277 | .root_source_file = if (options.root_source_file) |lp| lp.dupe(owner) else null, | |
| 251 | .root_source_file = if (options.root_source_file) |lp| lp.dupe(graph) else null, | |
| 278 | 252 | .import_table = .empty, |
| 279 | 253 | .resolved_target = options.target, |
| 280 | 254 | .optimize = options.optimize, |
| ... | ... | @@ -305,7 +279,7 @@ pub fn init( |
| 305 | 279 | .no_builtin = options.no_builtin, |
| 306 | 280 | }; |
| 307 | 281 | |
| 308 | m.import_table.ensureUnusedCapacity(allocator, options.imports.len) catch @panic("OOM"); | |
| 282 | m.import_table.ensureUnusedCapacity(arena, options.imports.len) catch @panic("OOM"); | |
| 309 | 283 | for (options.imports) |dep| { |
| 310 | 284 | m.import_table.putAssumeCapacity(dep.name, dep.module); |
| 311 | 285 | } |
| ... | ... | @@ -317,15 +291,18 @@ pub fn init( |
| 317 | 291 | } |
| 318 | 292 | |
| 319 | 293 | pub fn create(owner: *std.Build, options: CreateOptions) *Module { |
| 320 | const m = owner.allocator.create(Module) catch @panic("OOM"); | |
| 294 | const graph = owner.graph; | |
| 295 | const arena = graph.arena; | |
| 296 | const m = arena.create(Module) catch @panic("OOM"); | |
| 321 | 297 | m.init(owner, .{ .options = options }); |
| 322 | 298 | return m; |
| 323 | 299 | } |
| 324 | 300 | |
| 325 | 301 | /// Adds an existing module to be used with `@import`. |
| 326 | 302 | pub fn addImport(m: *Module, name: []const u8, module: *Module) void { |
| 327 | const b = m.owner; | |
| 328 | m.import_table.put(b.allocator, b.dupe(name), module) catch @panic("OOM"); | |
| 303 | const graph = m.owner.graph; | |
| 304 | const arena = graph.arena; | |
| 305 | m.import_table.put(arena, graph.dupeString(name), module) catch @panic("OOM"); | |
| 329 | 306 | } |
| 330 | 307 | |
| 331 | 308 | /// Creates a new module and adds it to be used with `@import`. |
| ... | ... | @@ -365,7 +342,8 @@ pub fn linkSystemLibrary( |
| 365 | 342 | name: []const u8, |
| 366 | 343 | options: LinkSystemLibraryOptions, |
| 367 | 344 | ) void { |
| 368 | const b = m.owner; | |
| 345 | const graph = m.owner.graph; | |
| 346 | const arena = graph.arena; | |
| 369 | 347 | |
| 370 | 348 | const target = m.requireKnownTarget(); |
| 371 | 349 | if (std.zig.target.isLibCLibName(target, name)) { |
| ... | ... | @@ -377,9 +355,9 @@ pub fn linkSystemLibrary( |
| 377 | 355 | return; |
| 378 | 356 | } |
| 379 | 357 | |
| 380 | m.link_objects.append(b.allocator, .{ | |
| 358 | m.link_objects.append(arena, .{ | |
| 381 | 359 | .system_lib = .{ |
| 382 | .name = b.dupe(name), | |
| 360 | .name = graph.dupeString(name), | |
| 383 | 361 | .needed = options.needed, |
| 384 | 362 | .weak = options.weak, |
| 385 | 363 | .use_pkg_config = options.use_pkg_config, |
| ... | ... | @@ -390,8 +368,9 @@ pub fn linkSystemLibrary( |
| 390 | 368 | } |
| 391 | 369 | |
| 392 | 370 | pub fn linkFramework(m: *Module, name: []const u8, options: LinkFrameworkOptions) void { |
| 393 | const b = m.owner; | |
| 394 | m.frameworks.put(b.allocator, b.dupe(name), options) catch @panic("OOM"); | |
| 371 | const graph = m.owner.graph; | |
| 372 | const arena = graph.arena; | |
| 373 | m.frameworks.put(arena, graph.dupeString(name), options) catch @panic("OOM"); | |
| 395 | 374 | } |
| 396 | 375 | |
| 397 | 376 | pub const AddCSourceFilesOptions = struct { |
| ... | ... | @@ -407,7 +386,8 @@ pub const AddCSourceFilesOptions = struct { |
| 407 | 386 | /// Handy when you have many non-Zig source files and want them all to have the same flags. |
| 408 | 387 | pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void { |
| 409 | 388 | const b = m.owner; |
| 410 | const allocator = b.allocator; | |
| 389 | const graph = m.owner.graph; | |
| 390 | const arena = graph.arena; | |
| 411 | 391 | |
| 412 | 392 | for (options.files) |path| { |
| 413 | 393 | if (std.fs.path.isAbsolute(path)) { |
| ... | ... | @@ -418,48 +398,50 @@ pub fn addCSourceFiles(m: *Module, options: AddCSourceFilesOptions) void { |
| 418 | 398 | } |
| 419 | 399 | } |
| 420 | 400 | |
| 421 | const c_source_files = allocator.create(CSourceFiles) catch @panic("OOM"); | |
| 401 | const c_source_files = arena.create(CSourceFiles) catch @panic("OOM"); | |
| 422 | 402 | c_source_files.* = .{ |
| 423 | 403 | .root = options.root orelse b.path(""), |
| 424 | 404 | .files = b.dupeStrings(options.files), |
| 425 | 405 | .flags = b.dupeStrings(options.flags), |
| 426 | 406 | .language = options.language, |
| 427 | 407 | }; |
| 428 | m.link_objects.append(allocator, .{ .c_source_files = c_source_files }) catch @panic("OOM"); | |
| 408 | m.link_objects.append(arena, .{ .c_source_files = c_source_files }) catch @panic("OOM"); | |
| 429 | 409 | } |
| 430 | 410 | |
| 431 | 411 | pub fn addCSourceFile(m: *Module, source: CSourceFile) void { |
| 432 | const b = m.owner; | |
| 433 | const allocator = b.allocator; | |
| 434 | const c_source_file = allocator.create(CSourceFile) catch @panic("OOM"); | |
| 435 | c_source_file.* = source.dupe(b); | |
| 436 | m.link_objects.append(allocator, .{ .c_source_file = c_source_file }) catch @panic("OOM"); | |
| 412 | const graph = m.owner.graph; | |
| 413 | const arena = graph.arena; | |
| 414 | const c_source_file = arena.create(CSourceFile) catch @panic("OOM"); | |
| 415 | c_source_file.* = source.dupe(graph); | |
| 416 | m.link_objects.append(arena, .{ .c_source_file = c_source_file }) catch @panic("OOM"); | |
| 437 | 417 | } |
| 438 | 418 | |
| 439 | 419 | /// Resource files must have the extension `.rc`. |
| 440 | 420 | /// Can be called regardless of target. The .rc file will be ignored |
| 441 | 421 | /// if the target object format does not support embedded resources. |
| 442 | 422 | pub fn addWin32ResourceFile(m: *Module, source: RcSourceFile) void { |
| 443 | const b = m.owner; | |
| 444 | const allocator = b.allocator; | |
| 423 | const graph = m.owner.graph; | |
| 424 | const arena = graph.arena; | |
| 445 | 425 | const target = m.requireKnownTarget(); |
| 446 | 426 | // Only the PE/COFF format has a Resource Table, so for any other target |
| 447 | 427 | // the resource file is ignored. |
| 448 | 428 | if (target.ofmt != .coff) return; |
| 449 | 429 | |
| 450 | const rc_source_file = allocator.create(RcSourceFile) catch @panic("OOM"); | |
| 451 | rc_source_file.* = source.dupe(b); | |
| 452 | m.link_objects.append(allocator, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM"); | |
| 430 | const rc_source_file = arena.create(RcSourceFile) catch @panic("OOM"); | |
| 431 | rc_source_file.* = source.dupe(graph); | |
| 432 | m.link_objects.append(arena, .{ .win32_resource_file = rc_source_file }) catch @panic("OOM"); | |
| 453 | 433 | } |
| 454 | 434 | |
| 455 | 435 | pub fn addAssemblyFile(m: *Module, source: LazyPath) void { |
| 456 | const b = m.owner; | |
| 457 | m.link_objects.append(b.allocator, .{ .assembly_file = source.dupe(b) }) catch @panic("OOM"); | |
| 436 | const graph = m.owner.graph; | |
| 437 | const arena = graph.arena; | |
| 438 | m.link_objects.append(arena, .{ .assembly_file = source.dupe(graph) }) catch @panic("OOM"); | |
| 458 | 439 | } |
| 459 | 440 | |
| 460 | 441 | pub fn addObjectFile(m: *Module, object: LazyPath) void { |
| 461 | const b = m.owner; | |
| 462 | m.link_objects.append(b.allocator, .{ .static_path = object.dupe(b) }) catch @panic("OOM"); | |
| 442 | const graph = m.owner.graph; | |
| 443 | const arena = graph.arena; | |
| 444 | m.link_objects.append(arena, .{ .static_path = object.dupe(graph) }) catch @panic("OOM"); | |
| 463 | 445 | } |
| 464 | 446 | |
| 465 | 447 | pub fn addObject(m: *Module, object: *Step.Compile) void { |
| ... | ... | @@ -473,55 +455,63 @@ pub fn linkLibrary(m: *Module, library: *Step.Compile) void { |
| 473 | 455 | } |
| 474 | 456 | |
| 475 | 457 | pub fn addAfterIncludePath(m: *Module, lazy_path: LazyPath) void { |
| 476 | const b = m.owner; | |
| 477 | m.include_dirs.append(b.allocator, .{ .path_after = lazy_path.dupe(b) }) catch @panic("OOM"); | |
| 458 | const graph = m.owner.graph; | |
| 459 | const arena = graph.arena; | |
| 460 | m.include_dirs.append(arena, .{ .path_after = lazy_path.dupe(graph) }) catch @panic("OOM"); | |
| 478 | 461 | } |
| 479 | 462 | |
| 480 | 463 | pub fn addSystemIncludePath(m: *Module, lazy_path: LazyPath) void { |
| 481 | const b = m.owner; | |
| 482 | m.include_dirs.append(b.allocator, .{ .path_system = lazy_path.dupe(b) }) catch @panic("OOM"); | |
| 464 | const graph = m.owner.graph; | |
| 465 | const arena = graph.arena; | |
| 466 | m.include_dirs.append(arena, .{ .path_system = lazy_path.dupe(graph) }) catch @panic("OOM"); | |
| 483 | 467 | } |
| 484 | 468 | |
| 485 | 469 | pub fn addIncludePath(m: *Module, lazy_path: LazyPath) void { |
| 486 | const b = m.owner; | |
| 487 | m.include_dirs.append(b.allocator, .{ .path = lazy_path.dupe(b) }) catch @panic("OOM"); | |
| 470 | const graph = m.owner.graph; | |
| 471 | const arena = graph.arena; | |
| 472 | m.include_dirs.append(arena, .{ .path = lazy_path.dupe(graph) }) catch @panic("OOM"); | |
| 488 | 473 | } |
| 489 | 474 | |
| 490 | 475 | pub fn addConfigHeader(m: *Module, config_header: *Step.ConfigHeader) void { |
| 491 | const allocator = m.owner.allocator; | |
| 492 | m.include_dirs.append(allocator, .{ .config_header_step = config_header }) catch @panic("OOM"); | |
| 476 | const graph = m.owner.graph; | |
| 477 | const arena = graph.arena; | |
| 478 | m.include_dirs.append(arena, .{ .config_header_step = config_header }) catch @panic("OOM"); | |
| 493 | 479 | } |
| 494 | 480 | |
| 495 | 481 | pub fn addSystemFrameworkPath(m: *Module, directory_path: LazyPath) void { |
| 496 | const b = m.owner; | |
| 497 | m.include_dirs.append(b.allocator, .{ .framework_path_system = directory_path.dupe(b) }) catch | |
| 498 | @panic("OOM"); | |
| 482 | const graph = m.owner.graph; | |
| 483 | const arena = graph.arena; | |
| 484 | m.include_dirs.append(arena, .{ .framework_path_system = directory_path.dupe(graph) }) catch @panic("OOM"); | |
| 499 | 485 | } |
| 500 | 486 | |
| 501 | 487 | pub fn addFrameworkPath(m: *Module, directory_path: LazyPath) void { |
| 502 | const b = m.owner; | |
| 503 | m.include_dirs.append(b.allocator, .{ .framework_path = directory_path.dupe(b) }) catch | |
| 504 | @panic("OOM"); | |
| 488 | const graph = m.owner.graph; | |
| 489 | const arena = graph.arena; | |
| 490 | m.include_dirs.append(arena, .{ .framework_path = directory_path.dupe(graph) }) catch @panic("OOM"); | |
| 505 | 491 | } |
| 506 | 492 | |
| 507 | 493 | pub fn addEmbedPath(m: *Module, lazy_path: LazyPath) void { |
| 508 | const b = m.owner; | |
| 509 | m.include_dirs.append(b.allocator, .{ .embed_path = lazy_path.dupe(b) }) catch @panic("OOM"); | |
| 494 | const graph = m.owner.graph; | |
| 495 | const arena = graph.arena; | |
| 496 | m.include_dirs.append(arena, .{ .embed_path = lazy_path.dupe(graph) }) catch @panic("OOM"); | |
| 510 | 497 | } |
| 511 | 498 | |
| 512 | 499 | pub fn addLibraryPath(m: *Module, directory_path: LazyPath) void { |
| 513 | const b = m.owner; | |
| 514 | m.lib_paths.append(b.allocator, directory_path.dupe(b)) catch @panic("OOM"); | |
| 500 | const graph = m.owner.graph; | |
| 501 | const arena = graph.arena; | |
| 502 | m.lib_paths.append(arena, directory_path.dupe(graph)) catch @panic("OOM"); | |
| 515 | 503 | } |
| 516 | 504 | |
| 517 | 505 | pub fn addRPath(m: *Module, directory_path: LazyPath) void { |
| 518 | const b = m.owner; | |
| 519 | m.rpaths.append(b.allocator, .{ .lazy_path = directory_path.dupe(b) }) catch @panic("OOM"); | |
| 506 | const graph = m.owner.graph; | |
| 507 | const arena = graph.arena; | |
| 508 | m.rpaths.append(arena, .{ .lazy_path = directory_path.dupe(graph) }) catch @panic("OOM"); | |
| 520 | 509 | } |
| 521 | 510 | |
| 522 | 511 | pub fn addRPathSpecial(m: *Module, bytes: []const u8) void { |
| 523 | const b = m.owner; | |
| 524 | m.rpaths.append(b.allocator, .{ .special = b.dupe(bytes) }) catch @panic("OOM"); | |
| 512 | const graph = m.owner.graph; | |
| 513 | const arena = graph.arena; | |
| 514 | m.rpaths.append(arena, .{ .special = graph.dupeString(bytes) }) catch @panic("OOM"); | |
| 525 | 515 | } |
| 526 | 516 | |
| 527 | 517 | /// Equvialent to the following C code, applied to all C source files owned by |
| ... | ... | @@ -532,130 +522,23 @@ pub fn addRPathSpecial(m: *Module, bytes: []const u8) void { |
| 532 | 522 | /// `name` and `value` need not live longer than the function call. |
| 533 | 523 | pub fn addCMacro(m: *Module, name: []const u8, value: []const u8) void { |
| 534 | 524 | const b = m.owner; |
| 535 | m.c_macros.append(b.allocator, b.fmt("-D{s}={s}", .{ name, value })) catch @panic("OOM"); | |
| 536 | } | |
| 537 | ||
| 538 | pub fn appendZigProcessFlags( | |
| 539 | m: *Module, | |
| 540 | zig_args: *std.array_list.Managed([]const u8), | |
| 541 | asking_step: ?*Step, | |
| 542 | ) !void { | |
| 543 | const b = m.owner; | |
| 544 | ||
| 545 | try addFlag(zig_args, m.strip, "-fstrip", "-fno-strip"); | |
| 546 | try addFlag(zig_args, m.single_threaded, "-fsingle-threaded", "-fno-single-threaded"); | |
| 547 | try addFlag(zig_args, m.stack_check, "-fstack-check", "-fno-stack-check"); | |
| 548 | try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector"); | |
| 549 | try addFlag(zig_args, m.omit_frame_pointer, "-fomit-frame-pointer", "-fno-omit-frame-pointer"); | |
| 550 | try addFlag(zig_args, m.error_tracing, "-ferror-tracing", "-fno-error-tracing"); | |
| 551 | try addFlag(zig_args, m.sanitize_thread, "-fsanitize-thread", "-fno-sanitize-thread"); | |
| 552 | try addFlag(zig_args, m.fuzz, "-ffuzz", "-fno-fuzz"); | |
| 553 | try addFlag(zig_args, m.valgrind, "-fvalgrind", "-fno-valgrind"); | |
| 554 | try addFlag(zig_args, m.pic, "-fPIC", "-fno-PIC"); | |
| 555 | try addFlag(zig_args, m.red_zone, "-mred-zone", "-mno-red-zone"); | |
| 556 | try addFlag(zig_args, m.no_builtin, "-fno-builtin", "-fbuiltin"); | |
| 557 | ||
| 558 | if (m.sanitize_c) |sc| switch (sc) { | |
| 559 | .off => try zig_args.append("-fno-sanitize-c"), | |
| 560 | .trap => try zig_args.append("-fsanitize-c=trap"), | |
| 561 | .full => try zig_args.append("-fsanitize-c=full"), | |
| 562 | }; | |
| 563 | ||
| 564 | if (m.dwarf_format) |dwarf_format| { | |
| 565 | try zig_args.append(switch (dwarf_format) { | |
| 566 | .@"32" => "-gdwarf32", | |
| 567 | .@"64" => "-gdwarf64", | |
| 568 | }); | |
| 569 | } | |
| 570 | ||
| 571 | if (m.unwind_tables) |unwind_tables| { | |
| 572 | try zig_args.append(switch (unwind_tables) { | |
| 573 | .none => "-fno-unwind-tables", | |
| 574 | .sync => "-funwind-tables", | |
| 575 | .async => "-fasync-unwind-tables", | |
| 576 | }); | |
| 577 | } | |
| 578 | ||
| 579 | try zig_args.ensureUnusedCapacity(1); | |
| 580 | if (m.optimize) |optimize| switch (optimize) { | |
| 581 | .Debug => zig_args.appendAssumeCapacity("-ODebug"), | |
| 582 | .ReleaseSmall => zig_args.appendAssumeCapacity("-OReleaseSmall"), | |
| 583 | .ReleaseFast => zig_args.appendAssumeCapacity("-OReleaseFast"), | |
| 584 | .ReleaseSafe => zig_args.appendAssumeCapacity("-OReleaseSafe"), | |
| 585 | }; | |
| 586 | ||
| 587 | if (m.code_model != .default) { | |
| 588 | try zig_args.append("-mcmodel"); | |
| 589 | try zig_args.append(@tagName(m.code_model)); | |
| 590 | } | |
| 591 | ||
| 592 | if (m.resolved_target) |*target| { | |
| 593 | // Communicate the query via CLI since it's more compact. | |
| 594 | if (!target.query.isNative()) { | |
| 595 | try zig_args.appendSlice(&.{ | |
| 596 | "-target", try target.query.zigTriple(b.allocator), | |
| 597 | "-mcpu", try target.query.serializeCpuAlloc(b.allocator), | |
| 598 | }); | |
| 599 | if (target.query.dynamic_linker) |*dynamic_linker| { | |
| 600 | if (dynamic_linker.get()) |dynamic_linker_path| { | |
| 601 | try zig_args.append("--dynamic-linker"); | |
| 602 | try zig_args.append(dynamic_linker_path); | |
| 603 | } else { | |
| 604 | try zig_args.append("--no-dynamic-linker"); | |
| 605 | } | |
| 606 | } | |
| 607 | } | |
| 608 | } | |
| 609 | ||
| 610 | for (m.export_symbol_names) |symbol_name| { | |
| 611 | try zig_args.append(b.fmt("--export={s}", .{symbol_name})); | |
| 612 | } | |
| 613 | ||
| 614 | for (m.include_dirs.items) |include_dir| { | |
| 615 | try include_dir.appendZigProcessFlags(b, zig_args, asking_step); | |
| 616 | } | |
| 617 | ||
| 618 | try zig_args.appendSlice(m.c_macros.items); | |
| 619 | ||
| 620 | try zig_args.ensureUnusedCapacity(2 * m.lib_paths.items.len); | |
| 621 | for (m.lib_paths.items) |lib_path| { | |
| 622 | zig_args.appendAssumeCapacity("-L"); | |
| 623 | zig_args.appendAssumeCapacity(lib_path.getPath2(b, asking_step)); | |
| 624 | } | |
| 625 | ||
| 626 | try zig_args.ensureUnusedCapacity(2 * m.rpaths.items.len); | |
| 627 | for (m.rpaths.items) |rpath| switch (rpath) { | |
| 628 | .lazy_path => |lp| { | |
| 629 | zig_args.appendAssumeCapacity("-rpath"); | |
| 630 | zig_args.appendAssumeCapacity(lp.getPath2(b, asking_step)); | |
| 631 | }, | |
| 632 | .special => |bytes| { | |
| 633 | zig_args.appendAssumeCapacity("-rpath"); | |
| 634 | zig_args.appendAssumeCapacity(bytes); | |
| 635 | }, | |
| 636 | }; | |
| 637 | } | |
| 638 | ||
| 639 | fn addFlag( | |
| 640 | args: *std.array_list.Managed([]const u8), | |
| 641 | opt: ?bool, | |
| 642 | then_name: []const u8, | |
| 643 | else_name: []const u8, | |
| 644 | ) !void { | |
| 645 | const cond = opt orelse return; | |
| 646 | return args.append(if (cond) then_name else else_name); | |
| 525 | const graph = m.owner.graph; | |
| 526 | const arena = graph.arena; | |
| 527 | m.c_macros.append(arena, b.fmt("-D{s}={s}", .{ name, value })) catch @panic("OOM"); | |
| 647 | 528 | } |
| 648 | 529 | |
| 649 | 530 | fn linkLibraryOrObject(m: *Module, other: *Step.Compile) void { |
| 650 | const allocator = m.owner.allocator; | |
| 531 | const graph = m.owner.graph; | |
| 532 | const arena = graph.arena; | |
| 533 | ||
| 651 | 534 | _ = other.getEmittedBin(); // Indicate there is a dependency on the outputted binary. |
| 652 | 535 | |
| 653 | 536 | if (other.rootModuleTarget().os.tag == .windows and other.isDynamicLibrary()) { |
| 654 | 537 | _ = other.getEmittedImplib(); // Indicate dependency on the outputted implib. |
| 655 | 538 | } |
| 656 | 539 | |
| 657 | m.link_objects.append(allocator, .{ .other_step = other }) catch @panic("OOM"); | |
| 658 | m.include_dirs.append(allocator, .{ .other_step = other }) catch @panic("OOM"); | |
| 540 | m.link_objects.append(arena, .{ .other_step = other }) catch @panic("OOM"); | |
| 541 | m.include_dirs.append(arena, .{ .other_step = other }) catch @panic("OOM"); | |
| 659 | 542 | } |
| 660 | 543 | |
| 661 | 544 | fn requireKnownTarget(m: *Module) *const std.Target { |
| ... | ... | @@ -670,11 +553,9 @@ pub const Graph = struct { |
| 670 | 553 | names: []const []const u8, |
| 671 | 554 | }; |
| 672 | 555 | |
| 673 | /// Intended to be used during the make phase only. | |
| 674 | /// | |
| 675 | /// Given that `root` is the root `Module` of a compilation, return all `Module`s | |
| 676 | /// in the module graph, including `root` itself. `root` is guaranteed to be the | |
| 677 | /// first module in the returned slice. | |
| 556 | /// Given that `root` is the root `Module` of a compilation, return all | |
| 557 | /// `Module` in the module graph, including `root` itself. `root` is guaranteed | |
| 558 | /// to be the first module in the returned slice. | |
| 678 | 559 | pub fn getGraph(root: *Module) Graph { |
| 679 | 560 | if (root.cached_graph.modules.len != 0) { |
| 680 | 561 | return root.cached_graph; |
| ... | ... | @@ -703,10 +584,3 @@ pub fn getGraph(root: *Module) Graph { |
| 703 | 584 | root.cached_graph = result; |
| 704 | 585 | return result; |
| 705 | 586 | } |
| 706 | ||
| 707 | const Module = @This(); | |
| 708 | const std = @import("std"); | |
| 709 | const assert = std.debug.assert; | |
| 710 | const LazyPath = std.Build.LazyPath; | |
| 711 | const Step = std.Build.Step; | |
| 712 | const ArrayList = std.ArrayList; |
lib/std/Build/Step.zig+43-910| ... | ... | @@ -1,33 +1,15 @@ |
| 1 | 1 | const Step = @This(); |
| 2 | const builtin = @import("builtin"); | |
| 3 | 2 | |
| 4 | 3 | const std = @import("../std.zig"); |
| 5 | const Io = std.Io; | |
| 6 | 4 | const Build = std.Build; |
| 7 | const Allocator = std.mem.Allocator; | |
| 8 | 5 | const assert = std.debug.assert; |
| 9 | const Cache = Build.Cache; | |
| 10 | const Path = Cache.Path; | |
| 11 | const ArrayList = std.ArrayList; | |
| 6 | const Configuration = std.Build.Configuration; | |
| 12 | 7 | |
| 13 | id: Id, | |
| 8 | tag: Configuration.Step.Tag, | |
| 14 | 9 | name: []const u8, |
| 15 | 10 | owner: *Build, |
| 16 | makeFn: MakeFn, | |
| 17 | 11 | |
| 18 | dependencies: std.array_list.Managed(*Step), | |
| 19 | /// This field is empty during execution of the user's build script, and | |
| 20 | /// then populated during dependency loop checking in the build runner. | |
| 21 | dependants: ArrayList(*Step), | |
| 22 | /// Collects the set of files that retrigger this step to run. | |
| 23 | /// | |
| 24 | /// This is used by the build system's implementation of `--watch` but it can | |
| 25 | /// also be potentially useful for IDEs to know what effects editing a | |
| 26 | /// particular file has. | |
| 27 | /// | |
| 28 | /// Populated within `make`. Implementation may choose to clear and repopulate, | |
| 29 | /// retain previous value, or update. | |
| 30 | inputs: Inputs, | |
| 12 | dependencies: std.ArrayList(*Step), | |
| 31 | 13 | |
| 32 | 14 | /// Set this field to declare an upper bound on the amount of bytes of memory it will |
| 33 | 15 | /// take to run the step. Zero means no limit. |
| ... | ... | @@ -50,181 +32,60 @@ inputs: Inputs, |
| 50 | 32 | /// total system memory available. |
| 51 | 33 | max_rss: usize, |
| 52 | 34 | |
| 53 | state: State, | |
| 54 | pending_deps: u32, | |
| 55 | ||
| 56 | result_error_msgs: ArrayList([]const u8), | |
| 57 | result_error_bundle: std.zig.ErrorBundle, | |
| 58 | result_stderr: []const u8, | |
| 59 | result_cached: bool, | |
| 60 | result_duration_ns: ?u64, | |
| 61 | /// 0 means unavailable or not reported. | |
| 62 | result_peak_rss: usize, | |
| 63 | /// If the step is failed and this field is populated, this is the command which failed. | |
| 64 | /// This field may be populated even if the step succeeded. | |
| 65 | result_failed_command: ?[]const u8, | |
| 66 | test_results: TestResults, | |
| 67 | ||
| 68 | 35 | /// The return address associated with creation of this step that can be useful |
| 69 | 36 | /// to print along with debugging messages. |
| 70 | 37 | debug_stack_trace: std.debug.StackTrace, |
| 71 | 38 | |
| 72 | pub const TestResults = struct { | |
| 73 | /// The total number of tests in the step. Every test has a "status" from the following: | |
| 74 | /// * passed | |
| 75 | /// * skipped | |
| 76 | /// * failed cleanly | |
| 77 | /// * crashed | |
| 78 | /// * timed out | |
| 79 | test_count: u32 = 0, | |
| 80 | ||
| 81 | /// The number of tests which were skipped (`error.SkipZigTest`). | |
| 82 | skip_count: u32 = 0, | |
| 83 | /// The number of tests which failed cleanly. | |
| 84 | fail_count: u32 = 0, | |
| 85 | /// The number of tests which terminated unexpectedly, i.e. crashed. | |
| 86 | crash_count: u32 = 0, | |
| 87 | /// The number of tests which timed out. | |
| 88 | timeout_count: u32 = 0, | |
| 89 | ||
| 90 | /// The number of detected memory leaks. The associated test may still have passed; indeed, *all* | |
| 91 | /// individual tests may have passed. However, the step as a whole fails if any test has leaks. | |
| 92 | leak_count: u32 = 0, | |
| 93 | /// The number of detected error logs. The associated test may still have passed; indeed, *all* | |
| 94 | /// individual tests may have passed. However, the step as a whole fails if any test logs errors. | |
| 95 | log_err_count: u32 = 0, | |
| 96 | ||
| 97 | pub fn isSuccess(tr: TestResults) bool { | |
| 98 | // all steps are success or skip | |
| 99 | return tr.fail_count == 0 and | |
| 100 | tr.crash_count == 0 and | |
| 101 | tr.timeout_count == 0 and | |
| 102 | // no (otherwise successful) step leaked memory or logged errors | |
| 103 | tr.leak_count == 0 and | |
| 104 | tr.log_err_count == 0; | |
| 105 | } | |
| 106 | ||
| 107 | /// Computes the number of tests which passed from the other values. | |
| 108 | pub fn passCount(tr: TestResults) u32 { | |
| 109 | return tr.test_count - tr.skip_count - tr.fail_count - tr.crash_count - tr.timeout_count; | |
| 110 | } | |
| 111 | }; | |
| 112 | ||
| 113 | pub const MakeOptions = struct { | |
| 114 | progress_node: std.Progress.Node, | |
| 115 | watch: bool, | |
| 116 | web_server: ?*Build.WebServer, | |
| 117 | /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds. | |
| 118 | unit_test_timeout_ns: ?u64, | |
| 119 | /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`. | |
| 120 | gpa: Allocator, | |
| 121 | }; | |
| 122 | ||
| 123 | pub const MakeFn = *const fn (step: *Step, options: MakeOptions) anyerror!void; | |
| 124 | ||
| 125 | pub const State = enum { | |
| 126 | precheck_unstarted, | |
| 127 | precheck_started, | |
| 128 | /// This is also used to indicate "dirty" steps that have been modified | |
| 129 | /// after a previous build completed, in which case, the step may or may | |
| 130 | /// not have been completed before. Either way, one or more of its direct | |
| 131 | /// file system inputs have been modified, meaning that the step needs to | |
| 132 | /// be re-evaluated. | |
| 133 | precheck_done, | |
| 134 | dependency_failure, | |
| 135 | success, | |
| 136 | failure, | |
| 137 | /// This state indicates that the step did not complete, however, it also did not fail, | |
| 138 | /// and it is safe to continue executing its dependencies. | |
| 139 | skipped, | |
| 140 | /// This step was skipped because it specified a max_rss that exceeded the runner's maximum. | |
| 141 | /// It is not safe to run its dependencies. | |
| 142 | skipped_oom, | |
| 143 | }; | |
| 144 | ||
| 145 | pub const Id = enum { | |
| 146 | top_level, | |
| 147 | compile, | |
| 148 | install_artifact, | |
| 149 | install_file, | |
| 150 | install_dir, | |
| 151 | remove_dir, | |
| 152 | fail, | |
| 153 | fmt, | |
| 154 | translate_c, | |
| 155 | write_file, | |
| 156 | update_source_files, | |
| 157 | run, | |
| 158 | check_file, | |
| 159 | check_object, | |
| 160 | config_header, | |
| 161 | objcopy, | |
| 162 | options, | |
| 163 | custom, | |
| 164 | ||
| 165 | pub fn Type(comptime id: Id) type { | |
| 166 | return switch (id) { | |
| 167 | .top_level => Build.TopLevelStep, | |
| 168 | .compile => Compile, | |
| 169 | .install_artifact => InstallArtifact, | |
| 170 | .install_file => InstallFile, | |
| 171 | .install_dir => InstallDir, | |
| 172 | .fail => Fail, | |
| 173 | .fmt => Fmt, | |
| 174 | .translate_c => TranslateC, | |
| 175 | .write_file => WriteFile, | |
| 176 | .update_source_files => UpdateSourceFiles, | |
| 177 | .run => Run, | |
| 178 | .check_file => CheckFile, | |
| 179 | .config_header => ConfigHeader, | |
| 180 | .objcopy => ObjCopy, | |
| 181 | .options => Options, | |
| 182 | .custom => @compileError("no type available for custom step"), | |
| 183 | }; | |
| 184 | } | |
| 185 | }; | |
| 39 | pub const Tag = Configuration.Step.Tag; | |
| 40 | ||
| 41 | pub fn Type(comptime tag: Tag) type { | |
| 42 | return switch (tag) { | |
| 43 | .check_file => CheckFile, | |
| 44 | .compile => Compile, | |
| 45 | .config_header => ConfigHeader, | |
| 46 | .fail => Fail, | |
| 47 | .find_program => FindProgram, | |
| 48 | .fmt => Fmt, | |
| 49 | .install_artifact => InstallArtifact, | |
| 50 | .install_dir => InstallDir, | |
| 51 | .install_file => InstallFile, | |
| 52 | .obj_copy => ObjCopy, | |
| 53 | .options => Options, | |
| 54 | .run => Run, | |
| 55 | .top_level => TopLevel, | |
| 56 | .translate_c => TranslateC, | |
| 57 | .update_source_files => UpdateSourceFiles, | |
| 58 | .write_file => WriteFile, | |
| 59 | }; | |
| 60 | } | |
| 186 | 61 | |
| 187 | 62 | pub const CheckFile = @import("Step/CheckFile.zig"); |
| 63 | pub const Compile = @import("Step/Compile.zig"); | |
| 188 | 64 | pub const ConfigHeader = @import("Step/ConfigHeader.zig"); |
| 189 | 65 | pub const Fail = @import("Step/Fail.zig"); |
| 66 | pub const FindProgram = @import("Step/FindProgram.zig"); | |
| 190 | 67 | pub const Fmt = @import("Step/Fmt.zig"); |
| 191 | 68 | pub const InstallArtifact = @import("Step/InstallArtifact.zig"); |
| 192 | 69 | pub const InstallDir = @import("Step/InstallDir.zig"); |
| 193 | 70 | pub const InstallFile = @import("Step/InstallFile.zig"); |
| 194 | 71 | pub const ObjCopy = @import("Step/ObjCopy.zig"); |
| 195 | pub const Compile = @import("Step/Compile.zig"); | |
| 196 | 72 | pub const Options = @import("Step/Options.zig"); |
| 197 | 73 | pub const Run = @import("Step/Run.zig"); |
| 198 | 74 | pub const TranslateC = @import("Step/TranslateC.zig"); |
| 199 | pub const WriteFile = @import("Step/WriteFile.zig"); | |
| 200 | 75 | pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig"); |
| 76 | pub const WriteFile = @import("Step/WriteFile.zig"); | |
| 201 | 77 | |
| 202 | pub const Inputs = struct { | |
| 203 | table: Table, | |
| 204 | ||
| 205 | pub const init: Inputs = .{ | |
| 206 | .table = .{}, | |
| 207 | }; | |
| 208 | ||
| 209 | pub const Table = std.ArrayHashMapUnmanaged(Build.Cache.Path, Files, Build.Cache.Path.TableAdapter, false); | |
| 210 | /// The special file name "." means any changes inside the directory. | |
| 211 | pub const Files = ArrayList([]const u8); | |
| 212 | ||
| 213 | pub fn populated(inputs: *Inputs) bool { | |
| 214 | return inputs.table.count() != 0; | |
| 215 | } | |
| 78 | pub const TopLevel = struct { | |
| 79 | pub const base_tag: Step.Tag = .top_level; | |
| 216 | 80 | |
| 217 | pub fn clear(inputs: *Inputs, gpa: Allocator) void { | |
| 218 | for (inputs.table.values()) |*files| files.deinit(gpa); | |
| 219 | inputs.table.clearRetainingCapacity(); | |
| 220 | } | |
| 81 | step: Step, | |
| 82 | description: []const u8, | |
| 221 | 83 | }; |
| 222 | 84 | |
| 223 | 85 | pub const StepOptions = struct { |
| 224 | id: Id, | |
| 86 | tag: Tag, | |
| 225 | 87 | name: []const u8, |
| 226 | 88 | owner: *Build, |
| 227 | makeFn: MakeFn = makeNoOp, | |
| 228 | 89 | first_ret_addr: ?usize = null, |
| 229 | 90 | max_rss: usize = 0, |
| 230 | 91 | }; |
| ... | ... | @@ -233,97 +94,31 @@ pub fn init(options: StepOptions) Step { |
| 233 | 94 | const arena = options.owner.allocator; |
| 234 | 95 | |
| 235 | 96 | return .{ |
| 236 | .id = options.id, | |
| 97 | .tag = options.tag, | |
| 237 | 98 | .name = arena.dupe(u8, options.name) catch @panic("OOM"), |
| 238 | 99 | .owner = options.owner, |
| 239 | .makeFn = options.makeFn, | |
| 240 | .dependencies = std.array_list.Managed(*Step).init(arena), | |
| 241 | .dependants = .empty, | |
| 242 | .inputs = Inputs.init, | |
| 243 | .state = .precheck_unstarted, | |
| 244 | .pending_deps = undefined, // initialized by build runner | |
| 100 | .dependencies = .empty, | |
| 245 | 101 | .max_rss = options.max_rss, |
| 246 | 102 | .debug_stack_trace = blk: { |
| 247 | 103 | const addr_buf = arena.alloc(usize, options.owner.debug_stack_frames_count) catch @panic("OOM"); |
| 248 | 104 | const first_ret_addr = options.first_ret_addr orelse @returnAddress(); |
| 249 | 105 | break :blk std.debug.captureCurrentStackTrace(.{ .first_address = first_ret_addr }, addr_buf); |
| 250 | 106 | }, |
| 251 | .result_error_msgs = .empty, | |
| 252 | .result_error_bundle = std.zig.ErrorBundle.empty, | |
| 253 | .result_stderr = "", | |
| 254 | .result_cached = false, | |
| 255 | .result_duration_ns = null, | |
| 256 | .result_peak_rss = 0, | |
| 257 | .result_failed_command = null, | |
| 258 | .test_results = .{}, | |
| 259 | }; | |
| 260 | } | |
| 261 | ||
| 262 | /// If the Step's `make` function reports `error.MakeFailed`, it indicates they | |
| 263 | /// have already reported the error. Otherwise, we add a simple error report | |
| 264 | /// here. | |
| 265 | pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void { | |
| 266 | const arena = s.owner.allocator; | |
| 267 | const graph = s.owner.graph; | |
| 268 | const io = graph.io; | |
| 269 | ||
| 270 | var start_ts: ?Io.Timestamp = t: { | |
| 271 | if (!graph.time_report) break :t null; | |
| 272 | if (s.id == .compile) break :t null; | |
| 273 | if (s.id == .run and s.cast(Run).?.stdio == .zig_test) break :t null; | |
| 274 | break :t Io.Clock.awake.now(io); | |
| 275 | }; | |
| 276 | const make_result = s.makeFn(s, options); | |
| 277 | if (start_ts) |*ts| { | |
| 278 | const duration = ts.untilNow(io, .awake); | |
| 279 | options.web_server.?.updateTimeReportGeneric(s, duration); | |
| 280 | } | |
| 281 | ||
| 282 | make_result catch |err| switch (err) { | |
| 283 | error.MakeFailed, error.MakeSkipped => |e| return e, | |
| 284 | else => { | |
| 285 | s.result_error_msgs.append(arena, @errorName(err)) catch @panic("OOM"); | |
| 286 | return error.MakeFailed; | |
| 287 | }, | |
| 288 | 107 | }; |
| 289 | ||
| 290 | if (!s.test_results.isSuccess()) { | |
| 291 | return error.MakeFailed; | |
| 292 | } | |
| 293 | ||
| 294 | if (s.max_rss != 0 and s.result_peak_rss > s.max_rss) { | |
| 295 | const msg = std.fmt.allocPrint(arena, "memory usage peaked at {0B:.2} ({0d} bytes), exceeding the declared upper bound of {1B:.2} ({1d} bytes)", .{ | |
| 296 | s.result_peak_rss, s.max_rss, | |
| 297 | }) catch @panic("OOM"); | |
| 298 | s.result_error_msgs.append(arena, msg) catch @panic("OOM"); | |
| 299 | } | |
| 300 | 108 | } |
| 301 | 109 | |
| 302 | 110 | pub fn dependOn(step: *Step, other: *Step) void { |
| 303 | step.dependencies.append(other) catch @panic("OOM"); | |
| 304 | } | |
| 305 | ||
| 306 | fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void { | |
| 307 | _ = options; | |
| 308 | ||
| 309 | var all_cached = true; | |
| 310 | ||
| 311 | for (step.dependencies.items) |dep| { | |
| 312 | all_cached = all_cached and dep.result_cached; | |
| 313 | } | |
| 314 | ||
| 315 | step.result_cached = all_cached; | |
| 111 | const arena = step.owner.allocator; | |
| 112 | step.dependencies.append(arena, other) catch @panic("OOM"); | |
| 316 | 113 | } |
| 317 | 114 | |
| 318 | 115 | pub fn cast(step: *Step, comptime T: type) ?*T { |
| 319 | if (step.id == T.base_id) { | |
| 320 | return @fieldParentPtr("step", step); | |
| 321 | } | |
| 116 | if (step.tag == T.base_tag) return @fieldParentPtr("step", step); | |
| 322 | 117 | return null; |
| 323 | 118 | } |
| 324 | 119 | |
| 325 | 120 | /// For debugging purposes, prints identifying information about this Step. |
| 326 | pub fn dump(step: *Step, t: Io.Terminal) void { | |
| 121 | pub fn dump(step: *Step, t: std.Io.Terminal) void { | |
| 327 | 122 | const w = t.writer; |
| 328 | 123 | if (step.debug_stack_trace.return_addresses.len > 0) { |
| 329 | 124 | w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {}; |
| ... | ... | @@ -337,682 +132,20 @@ pub fn dump(step: *Step, t: Io.Terminal) void { |
| 337 | 132 | } |
| 338 | 133 | } |
| 339 | 134 | |
| 340 | /// Populates `s.result_failed_command`. | |
| 341 | pub fn captureChildProcess( | |
| 342 | s: *Step, | |
| 343 | gpa: Allocator, | |
| 344 | progress_node: std.Progress.Node, | |
| 345 | argv: []const []const u8, | |
| 346 | ) !std.process.RunResult { | |
| 347 | const graph = s.owner.graph; | |
| 348 | const arena = graph.arena; | |
| 349 | const io = graph.io; | |
| 350 | ||
| 351 | // If an error occurs, it's happened in this command: | |
| 352 | assert(s.result_failed_command == null); | |
| 353 | s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv); | |
| 354 | ||
| 355 | try handleChildProcUnsupported(s); | |
| 356 | try handleVerbose(s.owner, .inherit, argv); | |
| 357 | ||
| 358 | const result = std.process.run(arena, io, .{ | |
| 359 | .argv = argv, | |
| 360 | .environ_map = &graph.environ_map, | |
| 361 | .progress_node = progress_node, | |
| 362 | }) catch |err| return s.fail("failed to run {s}: {t}", .{ argv[0], err }); | |
| 363 | ||
| 364 | if (result.stderr.len > 0) { | |
| 365 | try s.result_error_msgs.append(arena, result.stderr); | |
| 366 | } | |
| 367 | ||
| 368 | return result; | |
| 369 | } | |
| 370 | ||
| 371 | pub fn fail(step: *Step, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, MakeFailed } { | |
| 372 | try step.addError(fmt, args); | |
| 373 | return error.MakeFailed; | |
| 374 | } | |
| 375 | ||
| 376 | pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void { | |
| 377 | const arena = step.owner.allocator; | |
| 378 | const msg = try std.fmt.allocPrint(arena, fmt, args); | |
| 379 | try step.result_error_msgs.append(arena, msg); | |
| 380 | } | |
| 381 | ||
| 382 | pub const ZigProcess = struct { | |
| 383 | child: std.process.Child, | |
| 384 | multi_reader_buffer: Io.File.MultiReader.Buffer(2), | |
| 385 | multi_reader: Io.File.MultiReader, | |
| 386 | progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn, | |
| 387 | ||
| 388 | pub const StreamEnum = enum { stdout, stderr }; | |
| 389 | ||
| 390 | pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void { | |
| 391 | zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null; | |
| 392 | } | |
| 393 | ||
| 394 | pub fn deinit(zp: *ZigProcess, io: Io) void { | |
| 395 | zp.child.kill(io); | |
| 396 | zp.multi_reader.deinit(); | |
| 397 | zp.* = undefined; | |
| 398 | } | |
| 399 | }; | |
| 400 | ||
| 401 | /// Assumes that argv contains `--listen=-` and that the process being spawned | |
| 402 | /// is the zig compiler - the same version that compiled the build runner. | |
| 403 | /// Populates `s.result_failed_command`. | |
| 404 | pub fn evalZigProcess( | |
| 405 | s: *Step, | |
| 406 | argv: []const []const u8, | |
| 407 | prog_node: std.Progress.Node, | |
| 408 | watch: bool, | |
| 409 | web_server: ?*Build.WebServer, | |
| 410 | gpa: Allocator, | |
| 411 | ) !?Path { | |
| 412 | const b = s.owner; | |
| 413 | const io = b.graph.io; | |
| 414 | ||
| 415 | // If an error occurs, it's happened in this command: | |
| 416 | assert(s.result_failed_command == null); | |
| 417 | s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv); | |
| 418 | ||
| 419 | if (s.getZigProcess()) |zp| update: { | |
| 420 | assert(watch); | |
| 421 | if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index); | |
| 422 | zp.progress_ipc_index = null; | |
| 423 | var exited = false; | |
| 424 | defer if (exited) { | |
| 425 | s.cast(Compile).?.zig_process = null; | |
| 426 | zp.deinit(io); | |
| 427 | gpa.destroy(zp); | |
| 428 | } else zp.saveState(prog_node); | |
| 429 | const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { | |
| 430 | error.BrokenPipe, error.EndOfStream => |reason| { | |
| 431 | std.log.info("{s} restart required: {t}", .{ argv[0], reason }); | |
| 432 | // Process restart required. | |
| 433 | const term = zp.child.wait(io) catch |e| { | |
| 434 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); | |
| 435 | }; | |
| 436 | _ = term; | |
| 437 | exited = true; | |
| 438 | break :update; | |
| 439 | }, | |
| 440 | else => |e| return e, | |
| 441 | }; | |
| 442 | ||
| 443 | if (s.result_error_bundle.errorMessageCount() > 0) { | |
| 444 | return s.fail("{d} compilation errors", .{s.result_error_bundle.errorMessageCount()}); | |
| 445 | } | |
| 446 | ||
| 447 | if (s.result_error_msgs.items.len > 0 and result == null) { | |
| 448 | // Crash detected. | |
| 449 | const term = zp.child.wait(io) catch |e| { | |
| 450 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); | |
| 451 | }; | |
| 452 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 453 | exited = true; | |
| 454 | try handleChildProcessTerm(s, term); | |
| 455 | return error.MakeFailed; | |
| 456 | } | |
| 457 | ||
| 458 | return result; | |
| 459 | } | |
| 460 | assert(argv.len != 0); | |
| 461 | ||
| 462 | try handleChildProcUnsupported(s); | |
| 463 | try handleVerbose(s.owner, .inherit, argv); | |
| 464 | ||
| 465 | const zp = try gpa.create(ZigProcess); | |
| 466 | defer if (!watch) gpa.destroy(zp); | |
| 467 | ||
| 468 | zp.child = std.process.spawn(io, .{ | |
| 469 | .argv = argv, | |
| 470 | .environ_map = &b.graph.environ_map, | |
| 471 | .stdin = .pipe, | |
| 472 | .stdout = .pipe, | |
| 473 | .stderr = .pipe, | |
| 474 | .request_resource_usage_statistics = true, | |
| 475 | .progress_node = prog_node, | |
| 476 | }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); | |
| 477 | ||
| 478 | zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{ | |
| 479 | zp.child.stdout.?, zp.child.stderr.?, | |
| 480 | }); | |
| 481 | if (watch) s.cast(Compile).?.zig_process = zp; | |
| 482 | defer if (!watch) zp.deinit(io); | |
| 483 | ||
| 484 | const result = result: { | |
| 485 | defer if (watch) zp.saveState(prog_node); | |
| 486 | break :result try zigProcessUpdate(s, zp, watch, web_server, gpa); | |
| 487 | }; | |
| 488 | ||
| 489 | if (!watch) { | |
| 490 | // Send EOF to stdin. | |
| 491 | zp.child.stdin.?.close(io); | |
| 492 | zp.child.stdin = null; | |
| 493 | ||
| 494 | const term = zp.child.wait(io) catch |err| { | |
| 495 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], err }); | |
| 496 | }; | |
| 497 | s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 498 | ||
| 499 | // Special handling for Compile step that is expecting compile errors. | |
| 500 | if (s.cast(Compile)) |compile| switch (term) { | |
| 501 | .exited => { | |
| 502 | // Note that the exit code may be 0 in this case due to the | |
| 503 | // compiler server protocol. | |
| 504 | if (compile.expect_errors != null) { | |
| 505 | return error.NeedCompileErrorCheck; | |
| 506 | } | |
| 507 | }, | |
| 508 | else => {}, | |
| 509 | }; | |
| 510 | ||
| 511 | try handleChildProcessTerm(s, term); | |
| 512 | } | |
| 513 | ||
| 514 | if (s.result_error_bundle.errorMessageCount() > 0) { | |
| 515 | return s.fail("{d} compilation errors", .{s.result_error_bundle.errorMessageCount()}); | |
| 516 | } | |
| 517 | ||
| 518 | return result; | |
| 519 | } | |
| 520 | ||
| 521 | /// Wrapper around `Io.Dir.updateFile` that handles verbose and error output. | |
| 522 | pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u8) !Io.Dir.PrevStatus { | |
| 523 | const b = s.owner; | |
| 524 | const io = b.graph.io; | |
| 525 | const src_path = src_lazy_path.getPath3(b, s); | |
| 526 | try handleVerbose(b, .inherit, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path }); | |
| 527 | return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| | |
| 528 | return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err }); | |
| 529 | } | |
| 530 | ||
| 531 | /// Wrapper around `Io.Dir.createDirPathStatus` that handles verbose and error output. | |
| 532 | pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus { | |
| 533 | const b = s.owner; | |
| 534 | const io = b.graph.io; | |
| 535 | try handleVerbose(b, .inherit, &.{ "install", "-d", dest_path }); | |
| 536 | return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err| | |
| 537 | return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); | |
| 538 | } | |
| 539 | ||
| 540 | fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.WebServer, gpa: Allocator) !?Path { | |
| 541 | const b = s.owner; | |
| 542 | const arena = b.allocator; | |
| 543 | const io = b.graph.io; | |
| 544 | ||
| 545 | const start_ts = Io.Clock.awake.now(io); | |
| 546 | ||
| 547 | try sendMessage(io, zp.child.stdin.?, .update); | |
| 548 | if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); | |
| 549 | ||
| 550 | var result: ?Path = null; | |
| 551 | var eos_err: error{EndOfStream}!void = {}; | |
| 552 | ||
| 553 | const stdout = zp.multi_reader.fileReader(0); | |
| 554 | ||
| 555 | while (true) { | |
| 556 | const Header = std.zig.Server.Message.Header; | |
| 557 | const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { | |
| 558 | error.EndOfStream => break, | |
| 559 | error.ReadFailed => return stdout.err.?, | |
| 560 | }; | |
| 561 | const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { | |
| 562 | error.EndOfStream => |e| { | |
| 563 | // Better to report the crash with stderr below, but we set | |
| 564 | // this in case the child exits successfully while violating | |
| 565 | // this protocol. | |
| 566 | eos_err = e; | |
| 567 | break; | |
| 568 | }, | |
| 569 | error.ReadFailed => return stdout.err.?, | |
| 570 | }; | |
| 571 | switch (header.tag) { | |
| 572 | .zig_version => { | |
| 573 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { | |
| 574 | return s.fail( | |
| 575 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 576 | .{ builtin.zig_version_string, body }, | |
| 577 | ); | |
| 578 | } | |
| 579 | }, | |
| 580 | .error_bundle => { | |
| 581 | s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); | |
| 582 | // This message indicates the end of the update. | |
| 583 | if (watch) break; | |
| 584 | }, | |
| 585 | .emit_digest => { | |
| 586 | const EmitDigest = std.zig.Server.Message.EmitDigest; | |
| 587 | const emit_digest: *align(1) const EmitDigest = @ptrCast(body); | |
| 588 | s.result_cached = emit_digest.flags.cache_hit; | |
| 589 | const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len]; | |
| 590 | result = .{ | |
| 591 | .root_dir = b.cache_root, | |
| 592 | .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)), | |
| 593 | }; | |
| 594 | }, | |
| 595 | .file_system_inputs => { | |
| 596 | s.clearWatchInputs(); | |
| 597 | var it = std.mem.splitScalar(u8, body, 0); | |
| 598 | while (it.next()) |prefixed_path| { | |
| 599 | const prefix_index: std.zig.Server.Message.PathPrefix = @enumFromInt(prefixed_path[0] - 1); | |
| 600 | const sub_path = try arena.dupe(u8, prefixed_path[1..]); | |
| 601 | const sub_path_dirname = std.fs.path.dirname(sub_path) orelse ""; | |
| 602 | switch (prefix_index) { | |
| 603 | .cwd => { | |
| 604 | const path: Build.Cache.Path = .{ | |
| 605 | .root_dir = Build.Cache.Directory.cwd(), | |
| 606 | .sub_path = sub_path_dirname, | |
| 607 | }; | |
| 608 | try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path)); | |
| 609 | }, | |
| 610 | .zig_lib => zl: { | |
| 611 | if (s.cast(Step.Compile)) |compile| { | |
| 612 | if (compile.zig_lib_dir) |zig_lib_dir| { | |
| 613 | const lp = try zig_lib_dir.join(arena, sub_path); | |
| 614 | try addWatchInput(s, lp); | |
| 615 | break :zl; | |
| 616 | } | |
| 617 | } | |
| 618 | const path: Build.Cache.Path = .{ | |
| 619 | .root_dir = s.owner.graph.zig_lib_directory, | |
| 620 | .sub_path = sub_path_dirname, | |
| 621 | }; | |
| 622 | try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path)); | |
| 623 | }, | |
| 624 | .local_cache => { | |
| 625 | const path: Build.Cache.Path = .{ | |
| 626 | .root_dir = b.cache_root, | |
| 627 | .sub_path = sub_path_dirname, | |
| 628 | }; | |
| 629 | try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path)); | |
| 630 | }, | |
| 631 | .global_cache => { | |
| 632 | const path: Build.Cache.Path = .{ | |
| 633 | .root_dir = s.owner.graph.global_cache_root, | |
| 634 | .sub_path = sub_path_dirname, | |
| 635 | }; | |
| 636 | try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path)); | |
| 637 | }, | |
| 638 | } | |
| 639 | } | |
| 640 | }, | |
| 641 | .time_report => if (web_server) |ws| { | |
| 642 | const TimeReport = std.zig.Server.Message.TimeReport; | |
| 643 | const tr: *align(1) const TimeReport = @ptrCast(body[0..@sizeOf(TimeReport)]); | |
| 644 | ws.updateTimeReportCompile(.{ | |
| 645 | .compile = s.cast(Step.Compile).?, | |
| 646 | .use_llvm = tr.flags.use_llvm, | |
| 647 | .stats = tr.stats, | |
| 648 | .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()), | |
| 649 | .llvm_pass_timings_len = tr.llvm_pass_timings_len, | |
| 650 | .files_len = tr.files_len, | |
| 651 | .decls_len = tr.decls_len, | |
| 652 | .trailing = body[@sizeOf(TimeReport)..], | |
| 653 | }); | |
| 654 | }, | |
| 655 | else => {}, // ignore other messages | |
| 656 | } | |
| 657 | } | |
| 658 | ||
| 659 | s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()); | |
| 660 | ||
| 661 | const stderr_contents = zp.multi_reader.reader(1).buffered(); | |
| 662 | if (stderr_contents.len > 0) { | |
| 663 | try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); | |
| 664 | } | |
| 665 | ||
| 666 | try eos_err; | |
| 667 | ||
| 668 | return result; | |
| 669 | } | |
| 670 | ||
| 671 | pub fn getZigProcess(s: *Step) ?*ZigProcess { | |
| 672 | return switch (s.id) { | |
| 673 | .compile => s.cast(Compile).?.zig_process, | |
| 674 | else => null, | |
| 675 | }; | |
| 676 | } | |
| 677 | ||
| 678 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | |
| 679 | const header: std.zig.Client.Message.Header = .{ | |
| 680 | .tag = tag, | |
| 681 | .bytes_len = 0, | |
| 682 | }; | |
| 683 | var w = file.writer(io, &.{}); | |
| 684 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 685 | error.WriteFailed => return w.err.?, | |
| 686 | }; | |
| 687 | } | |
| 688 | ||
| 689 | pub fn handleVerbose( | |
| 690 | b: *Build, | |
| 691 | cwd: std.process.Child.Cwd, | |
| 692 | argv: []const []const u8, | |
| 693 | ) error{OutOfMemory}!void { | |
| 694 | return handleVerbose2(b, cwd, null, argv); | |
| 695 | } | |
| 696 | ||
| 697 | pub fn handleVerbose2( | |
| 698 | b: *Build, | |
| 699 | cwd: std.process.Child.Cwd, | |
| 700 | opt_env: ?*const std.process.Environ.Map, | |
| 701 | argv: []const []const u8, | |
| 702 | ) error{OutOfMemory}!void { | |
| 703 | if (b.verbose) { | |
| 704 | const graph = b.graph; | |
| 705 | // Intention of verbose is to print all sub-process command lines to | |
| 706 | // stderr before spawning them. | |
| 707 | const text = try allocPrintCmd(b.allocator, cwd, if (opt_env) |env| .{ | |
| 708 | .child = env, | |
| 709 | .parent = &graph.environ_map, | |
| 710 | } else null, argv); | |
| 711 | std.debug.print("{s}\n", .{text}); | |
| 712 | } | |
| 713 | } | |
| 714 | ||
| 715 | /// Asserts that the caller has already populated `s.result_failed_command`. | |
| 716 | pub inline fn handleChildProcUnsupported(s: *Step) error{ OutOfMemory, MakeFailed }!void { | |
| 717 | if (!std.process.can_spawn) { | |
| 718 | return s.fail("unable to spawn process: host cannot spawn child processes", .{}); | |
| 719 | } | |
| 720 | } | |
| 721 | ||
| 722 | /// Asserts that the caller has already populated `s.result_failed_command`. | |
| 723 | pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ MakeFailed, OutOfMemory }!void { | |
| 724 | assert(s.result_failed_command != null); | |
| 725 | return switch (term) { | |
| 726 | .exited => |code| if (code != 0) s.fail("process exited with error code {d}", .{code}), | |
| 727 | .signal => |sig| s.fail("process terminated with signal {t}", .{sig}), | |
| 728 | .stopped => |sig| s.fail("process stopped with signal {t}", .{sig}), | |
| 729 | .unknown => s.fail("process terminated unexpectedly", .{}), | |
| 730 | }; | |
| 731 | } | |
| 732 | ||
| 733 | pub fn allocPrintCmd( | |
| 734 | gpa: Allocator, | |
| 735 | cwd: std.process.Child.Cwd, | |
| 736 | opt_env: ?struct { | |
| 737 | child: *const std.process.Environ.Map, | |
| 738 | parent: *const std.process.Environ.Map, | |
| 739 | }, | |
| 740 | argv: []const []const u8, | |
| 741 | ) Allocator.Error![]u8 { | |
| 742 | const shell = struct { | |
| 743 | fn escape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void { | |
| 744 | for (string) |c| { | |
| 745 | if (switch (c) { | |
| 746 | else => true, | |
| 747 | '%', '+'...':', '@'...'Z', '_', 'a'...'z' => false, | |
| 748 | '=' => is_argv0, | |
| 749 | }) break; | |
| 750 | } else return writer.writeAll(string); | |
| 751 | ||
| 752 | try writer.writeByte('"'); | |
| 753 | for (string) |c| { | |
| 754 | if (switch (c) { | |
| 755 | std.ascii.control_code.nul => break, | |
| 756 | '!', '"', '$', '\\', '`' => true, | |
| 757 | else => !std.ascii.isPrint(c), | |
| 758 | }) try writer.writeByte('\\'); | |
| 759 | switch (c) { | |
| 760 | std.ascii.control_code.nul => unreachable, | |
| 761 | std.ascii.control_code.bel => try writer.writeByte('a'), | |
| 762 | std.ascii.control_code.bs => try writer.writeByte('b'), | |
| 763 | std.ascii.control_code.ht => try writer.writeByte('t'), | |
| 764 | std.ascii.control_code.lf => try writer.writeByte('n'), | |
| 765 | std.ascii.control_code.vt => try writer.writeByte('v'), | |
| 766 | std.ascii.control_code.ff => try writer.writeByte('f'), | |
| 767 | std.ascii.control_code.cr => try writer.writeByte('r'), | |
| 768 | std.ascii.control_code.esc => try writer.writeByte('E'), | |
| 769 | ' '...'~' => try writer.writeByte(c), | |
| 770 | else => try writer.print("{o:0>3}", .{c}), | |
| 771 | } | |
| 772 | } | |
| 773 | try writer.writeByte('"'); | |
| 774 | } | |
| 775 | }; | |
| 776 | ||
| 777 | var aw: Io.Writer.Allocating = .init(gpa); | |
| 778 | defer aw.deinit(); | |
| 779 | const writer = &aw.writer; | |
| 780 | switch (cwd) { | |
| 781 | .inherit => {}, | |
| 782 | .path => |path| writer.print("cd {s} && ", .{path}) catch return error.OutOfMemory, | |
| 783 | .dir => @panic("TODO"), | |
| 784 | } | |
| 785 | if (opt_env) |env| { | |
| 786 | var it = env.child.iterator(); | |
| 787 | while (it.next()) |entry| { | |
| 788 | const key = entry.key_ptr.*; | |
| 789 | const value = entry.value_ptr.*; | |
| 790 | if (env.parent.get(key)) |process_value| { | |
| 791 | if (std.mem.eql(u8, value, process_value)) continue; | |
| 792 | } | |
| 793 | writer.print("{s}=", .{key}) catch return error.OutOfMemory; | |
| 794 | shell.escape(writer, value, false) catch return error.OutOfMemory; | |
| 795 | writer.writeByte(' ') catch return error.OutOfMemory; | |
| 796 | } | |
| 797 | } | |
| 798 | shell.escape(writer, argv[0], true) catch return error.OutOfMemory; | |
| 799 | for (argv[1..]) |arg| { | |
| 800 | writer.writeByte(' ') catch return error.OutOfMemory; | |
| 801 | shell.escape(writer, arg, false) catch return error.OutOfMemory; | |
| 802 | } | |
| 803 | return aw.toOwnedSlice(); | |
| 804 | } | |
| 805 | ||
| 806 | /// Prefer `cacheHitAndWatch` unless you already added watch inputs | |
| 807 | /// separately from using the cache system. | |
| 808 | pub fn cacheHit(s: *Step, man: *Build.Cache.Manifest) !bool { | |
| 809 | s.result_cached = man.hit() catch |err| return failWithCacheError(s, man, err); | |
| 810 | return s.result_cached; | |
| 811 | } | |
| 812 | ||
| 813 | /// Clears previous watch inputs, if any, and then populates watch inputs from | |
| 814 | /// the full set of files picked up by the cache manifest. | |
| 815 | /// | |
| 816 | /// Must be accompanied with `writeManifestAndWatch`. | |
| 817 | pub fn cacheHitAndWatch(s: *Step, man: *Build.Cache.Manifest) !bool { | |
| 818 | const is_hit = man.hit() catch |err| return failWithCacheError(s, man, err); | |
| 819 | s.result_cached = is_hit; | |
| 820 | // The above call to hit() populates the manifest with files, so in case of | |
| 821 | // a hit, we need to populate watch inputs. | |
| 822 | if (is_hit) try setWatchInputsFromManifest(s, man); | |
| 823 | return is_hit; | |
| 824 | } | |
| 825 | ||
| 826 | fn failWithCacheError( | |
| 827 | s: *Step, | |
| 828 | man: *const Build.Cache.Manifest, | |
| 829 | err: Build.Cache.Manifest.HitError, | |
| 830 | ) error{ OutOfMemory, Canceled, MakeFailed } { | |
| 831 | switch (err) { | |
| 832 | error.CacheCheckFailed => switch (man.diagnostic) { | |
| 833 | .none => unreachable, | |
| 834 | .manifest_create, .manifest_read, .manifest_lock => |e| return s.fail("failed to check cache: {t} {t}", .{ | |
| 835 | man.diagnostic, e, | |
| 836 | }), | |
| 837 | .file_open, .file_stat, .file_read, .file_hash => |op| { | |
| 838 | const pp = man.files.keys()[op.file_index].prefixed_path; | |
| 839 | const prefix = man.cache.prefixes()[pp.prefix].path orelse ""; | |
| 840 | return s.fail("failed to check cache: '{s}{c}{s}' {t} {t}", .{ | |
| 841 | prefix, std.fs.path.sep, pp.sub_path, man.diagnostic, op.err, | |
| 842 | }); | |
| 843 | }, | |
| 844 | }, | |
| 845 | error.OutOfMemory, error.Canceled => |e| return e, | |
| 846 | error.InvalidFormat => return s.fail("failed to check cache: invalid manifest file format", .{}), | |
| 847 | } | |
| 848 | } | |
| 849 | ||
| 850 | /// Prefer `writeManifestAndWatch` unless you already added watch inputs | |
| 851 | /// separately from using the cache system. | |
| 852 | pub fn writeManifest(s: *Step, man: *Build.Cache.Manifest) !void { | |
| 853 | if (s.test_results.isSuccess()) { | |
| 854 | man.writeManifest() catch |err| { | |
| 855 | try s.addError("unable to write cache manifest: {t}", .{err}); | |
| 856 | }; | |
| 857 | } | |
| 858 | } | |
| 859 | ||
| 860 | /// Clears previous watch inputs, if any, and then populates watch inputs from | |
| 861 | /// the full set of files picked up by the cache manifest. | |
| 862 | /// | |
| 863 | /// Must be accompanied with `cacheHitAndWatch`. | |
| 864 | pub fn writeManifestAndWatch(s: *Step, man: *Build.Cache.Manifest) !void { | |
| 865 | try writeManifest(s, man); | |
| 866 | try setWatchInputsFromManifest(s, man); | |
| 867 | } | |
| 868 | ||
| 869 | fn setWatchInputsFromManifest(s: *Step, man: *Build.Cache.Manifest) !void { | |
| 870 | const arena = s.owner.allocator; | |
| 871 | const prefixes = man.cache.prefixes(); | |
| 872 | clearWatchInputs(s); | |
| 873 | for (man.files.keys()) |file| { | |
| 874 | // The file path data is freed when the cache manifest is cleaned up at the end of `make`. | |
| 875 | const sub_path = try arena.dupe(u8, file.prefixed_path.sub_path); | |
| 876 | try addWatchInputFromPath(s, .{ | |
| 877 | .root_dir = prefixes[file.prefixed_path.prefix], | |
| 878 | .sub_path = std.fs.path.dirname(sub_path) orelse "", | |
| 879 | }, std.fs.path.basename(sub_path)); | |
| 880 | } | |
| 881 | } | |
| 882 | ||
| 883 | /// For steps that have a single input that never changes when re-running `make`. | |
| 884 | pub fn singleUnchangingWatchInput(step: *Step, lazy_path: Build.LazyPath) Allocator.Error!void { | |
| 885 | if (!step.inputs.populated()) try step.addWatchInput(lazy_path); | |
| 886 | } | |
| 887 | ||
| 888 | pub fn clearWatchInputs(step: *Step) void { | |
| 889 | const gpa = step.owner.allocator; | |
| 890 | step.inputs.clear(gpa); | |
| 891 | } | |
| 892 | ||
| 893 | /// Places a *file* dependency on the path. | |
| 894 | pub fn addWatchInput(step: *Step, lazy_file: Build.LazyPath) Allocator.Error!void { | |
| 895 | switch (lazy_file) { | |
| 896 | .src_path => |src_path| try addWatchInputFromBuilder(step, src_path.owner, src_path.sub_path), | |
| 897 | .dependency => |d| try addWatchInputFromBuilder(step, d.dependency.builder, d.sub_path), | |
| 898 | .cwd_relative => |path_string| { | |
| 899 | try addWatchInputFromPath(step, .{ | |
| 900 | .root_dir = .{ | |
| 901 | .path = null, | |
| 902 | .handle = Io.Dir.cwd(), | |
| 903 | }, | |
| 904 | .sub_path = std.fs.path.dirname(path_string) orelse "", | |
| 905 | }, std.fs.path.basename(path_string)); | |
| 906 | }, | |
| 907 | // Nothing to watch because this dependency edge is modeled instead via `dependants`. | |
| 908 | .generated => {}, | |
| 909 | } | |
| 910 | } | |
| 911 | ||
| 912 | /// Any changes inside the directory will trigger invalidation. | |
| 913 | /// | |
| 914 | /// See also `addDirectoryWatchInputFromPath` which takes a `Build.Cache.Path` instead. | |
| 915 | /// | |
| 916 | /// Paths derived from this directory should also be manually added via | |
| 917 | /// `addDirectoryWatchInputFromPath` if and only if this function returns | |
| 918 | /// `true`. | |
| 919 | pub fn addDirectoryWatchInput(step: *Step, lazy_directory: Build.LazyPath) Allocator.Error!bool { | |
| 920 | switch (lazy_directory) { | |
| 921 | .src_path => |src_path| try addDirectoryWatchInputFromBuilder(step, src_path.owner, src_path.sub_path), | |
| 922 | .dependency => |d| try addDirectoryWatchInputFromBuilder(step, d.dependency.builder, d.sub_path), | |
| 923 | .cwd_relative => |path_string| { | |
| 924 | try addDirectoryWatchInputFromPath(step, .{ | |
| 925 | .root_dir = .{ | |
| 926 | .path = null, | |
| 927 | .handle = Io.Dir.cwd(), | |
| 928 | }, | |
| 929 | .sub_path = path_string, | |
| 930 | }); | |
| 931 | }, | |
| 932 | // Nothing to watch because this dependency edge is modeled instead via `dependants`. | |
| 933 | .generated => return false, | |
| 934 | } | |
| 935 | return true; | |
| 936 | } | |
| 937 | ||
| 938 | /// Any changes inside the directory will trigger invalidation. | |
| 939 | /// | |
| 940 | /// See also `addDirectoryWatchInput` which takes a `Build.LazyPath` instead. | |
| 941 | /// | |
| 942 | /// This function should only be called when it has been verified that the | |
| 943 | /// dependency on `path` is not already accounted for by a `Step` dependency. | |
| 944 | /// In other words, before calling this function, first check that the | |
| 945 | /// `Build.LazyPath` which this `path` is derived from is not `generated`. | |
| 946 | pub fn addDirectoryWatchInputFromPath(step: *Step, path: Build.Cache.Path) !void { | |
| 947 | return addWatchInputFromPath(step, path, "."); | |
| 948 | } | |
| 949 | ||
| 950 | fn addWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void { | |
| 951 | return addWatchInputFromPath(step, .{ | |
| 952 | .root_dir = builder.build_root, | |
| 953 | .sub_path = std.fs.path.dirname(sub_path) orelse "", | |
| 954 | }, std.fs.path.basename(sub_path)); | |
| 955 | } | |
| 956 | ||
| 957 | fn addDirectoryWatchInputFromBuilder(step: *Step, builder: *Build, sub_path: []const u8) !void { | |
| 958 | return addDirectoryWatchInputFromPath(step, .{ | |
| 959 | .root_dir = builder.build_root, | |
| 960 | .sub_path = sub_path, | |
| 961 | }); | |
| 962 | } | |
| 963 | ||
| 964 | fn addWatchInputFromPath(step: *Step, path: Build.Cache.Path, basename: []const u8) !void { | |
| 965 | const gpa = step.owner.allocator; | |
| 966 | const gop = try step.inputs.table.getOrPut(gpa, path); | |
| 967 | if (!gop.found_existing) gop.value_ptr.* = .empty; | |
| 968 | try gop.value_ptr.append(gpa, basename); | |
| 969 | } | |
| 970 | ||
| 971 | /// Implementation detail of file watching and forced rebuilds. Prepares the step for being re-evaluated. | |
| 972 | pub fn reset(step: *Step, gpa: Allocator) void { | |
| 973 | assert(step.state == .precheck_done); | |
| 974 | ||
| 975 | if (step.result_failed_command) |cmd| gpa.free(cmd); | |
| 976 | ||
| 977 | step.result_error_msgs.clearRetainingCapacity(); | |
| 978 | step.result_stderr = ""; | |
| 979 | step.result_cached = false; | |
| 980 | step.result_duration_ns = null; | |
| 981 | step.result_peak_rss = 0; | |
| 982 | step.result_failed_command = null; | |
| 983 | step.test_results = .{}; | |
| 984 | step.clearWatchInputs(); | |
| 985 | ||
| 986 | step.result_error_bundle.deinit(gpa); | |
| 987 | step.result_error_bundle = std.zig.ErrorBundle.empty; | |
| 988 | } | |
| 989 | ||
| 990 | /// Implementation detail of file watching. Prepares the step for being re-evaluated. | |
| 991 | /// Returns `true` if the step was newly invalidated, `false` if it was already invalidated. | |
| 992 | pub fn invalidateResult(step: *Step, gpa: Allocator) bool { | |
| 993 | if (step.state == .precheck_done) return false; | |
| 994 | assert(step.pending_deps == 0); | |
| 995 | step.state = .precheck_done; | |
| 996 | step.reset(gpa); | |
| 997 | for (step.dependants.items) |dependant| { | |
| 998 | _ = dependant.invalidateResult(gpa); | |
| 999 | dependant.pending_deps += 1; | |
| 1000 | } | |
| 1001 | return true; | |
| 1002 | } | |
| 1003 | ||
| 1004 | 135 | test { |
| 1005 | 136 | _ = CheckFile; |
| 137 | _ = Compile; | |
| 138 | _ = ConfigHeader; | |
| 1006 | 139 | _ = Fail; |
| 140 | _ = FindProgram; | |
| 1007 | 141 | _ = Fmt; |
| 1008 | 142 | _ = InstallArtifact; |
| 1009 | 143 | _ = InstallDir; |
| 1010 | 144 | _ = InstallFile; |
| 1011 | 145 | _ = ObjCopy; |
| 1012 | _ = Compile; | |
| 1013 | 146 | _ = Options; |
| 1014 | 147 | _ = Run; |
| 1015 | 148 | _ = TranslateC; |
| 1016 | _ = WriteFile; | |
| 1017 | 149 | _ = UpdateSourceFiles; |
| 150 | _ = WriteFile; | |
| 1018 | 151 | } |
lib/std/Build/Step/CheckFile.zig+17-63| ... | ... | @@ -1,7 +1,4 @@ |
| 1 | 1 | //! Fail the build step if a file does not match certain checks. |
| 2 | //! TODO: make this more flexible, supporting more kinds of checks. | |
| 3 | //! TODO: generalize the code in std.testing.expectEqualStrings and make this | |
| 4 | //! CheckFile step produce those helpful diagnostics when there is not a match. | |
| 5 | 2 | const CheckFile = @This(); |
| 6 | 3 | |
| 7 | 4 | const std = @import("std"); |
| ... | ... | @@ -9,83 +6,40 @@ const Io = std.Io; |
| 9 | 6 | const Step = std.Build.Step; |
| 10 | 7 | const fs = std.fs; |
| 11 | 8 | const mem = std.mem; |
| 9 | const Configuration = std.Build.Configuration; | |
| 12 | 10 | |
| 13 | 11 | step: Step, |
| 14 | expected_matches: []const []const u8, | |
| 15 | expected_exact: ?[]const u8, | |
| 16 | source: std.Build.LazyPath, | |
| 17 | max_bytes: usize = 20 * 1024 * 1024, | |
| 12 | file: std.Build.LazyPath, | |
| 13 | expected_matches: []const Configuration.Bytes, | |
| 14 | expected_exact: ?Configuration.Bytes, | |
| 15 | max_bytes: ?u32, | |
| 18 | 16 | |
| 19 | pub const base_id: Step.Id = .check_file; | |
| 17 | pub const base_tag: Step.Tag = .check_file; | |
| 20 | 18 | |
| 21 | 19 | pub const Options = struct { |
| 22 | 20 | expected_matches: []const []const u8 = &.{}, |
| 23 | 21 | expected_exact: ?[]const u8 = null, |
| 22 | max_bytes: ?u32 = null, | |
| 24 | 23 | }; |
| 25 | 24 | |
| 26 | pub fn create( | |
| 27 | owner: *std.Build, | |
| 28 | source: std.Build.LazyPath, | |
| 29 | options: Options, | |
| 30 | ) *CheckFile { | |
| 31 | const check_file = owner.allocator.create(CheckFile) catch @panic("OOM"); | |
| 25 | pub fn create(owner: *std.Build, file: std.Build.LazyPath, options: Options) *CheckFile { | |
| 26 | const graph = owner.graph; | |
| 27 | const check_file = graph.create(CheckFile); | |
| 32 | 28 | check_file.* = .{ |
| 33 | .step = Step.init(.{ | |
| 34 | .id = base_id, | |
| 29 | .step = .init(.{ | |
| 30 | .tag = base_tag, | |
| 35 | 31 | .name = "CheckFile", |
| 36 | 32 | .owner = owner, |
| 37 | .makeFn = make, | |
| 38 | 33 | }), |
| 39 | .source = source.dupe(owner), | |
| 40 | .expected_matches = owner.dupeStrings(options.expected_matches), | |
| 41 | .expected_exact = options.expected_exact, | |
| 34 | .file = file.dupe(graph), | |
| 35 | .expected_matches = graph.addBytesList(options.expected_matches), | |
| 36 | .expected_exact = if (options.expected_exact) |b| graph.addBytes(b) else null, | |
| 37 | .max_bytes = options.max_bytes, | |
| 42 | 38 | }; |
| 43 | check_file.source.addStepDependencies(&check_file.step); | |
| 39 | file.addStepDependencies(&check_file.step); | |
| 44 | 40 | return check_file; |
| 45 | 41 | } |
| 46 | 42 | |
| 47 | 43 | pub fn setName(check_file: *CheckFile, name: []const u8) void { |
| 48 | 44 | check_file.step.name = name; |
| 49 | 45 | } |
| 50 | ||
| 51 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 52 | _ = options; | |
| 53 | const b = step.owner; | |
| 54 | const io = b.graph.io; | |
| 55 | const check_file: *CheckFile = @fieldParentPtr("step", step); | |
| 56 | try step.singleUnchangingWatchInput(check_file.source); | |
| 57 | ||
| 58 | const src_path = check_file.source.getPath2(b, step); | |
| 59 | const contents = Io.Dir.cwd().readFileAlloc(io, src_path, b.allocator, .limited(check_file.max_bytes)) catch |err| { | |
| 60 | return step.fail("unable to read '{s}': {s}", .{ | |
| 61 | src_path, @errorName(err), | |
| 62 | }); | |
| 63 | }; | |
| 64 | ||
| 65 | for (check_file.expected_matches) |expected_match| { | |
| 66 | if (mem.find(u8, contents, expected_match) == null) { | |
| 67 | return step.fail( | |
| 68 | \\ | |
| 69 | \\========= expected to find: =================== | |
| 70 | \\{s} | |
| 71 | \\========= but file does not contain it: ======= | |
| 72 | \\{s} | |
| 73 | \\=============================================== | |
| 74 | , .{ expected_match, contents }); | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | if (check_file.expected_exact) |expected_exact| { | |
| 79 | if (!mem.eql(u8, expected_exact, contents)) { | |
| 80 | return step.fail( | |
| 81 | \\ | |
| 82 | \\========= expected: ===================== | |
| 83 | \\{s} | |
| 84 | \\========= but found: ==================== | |
| 85 | \\{s} | |
| 86 | \\========= from the following file: ====== | |
| 87 | \\{s} | |
| 88 | , .{ expected_exact, contents, src_path }); | |
| 89 | } | |
| 90 | } | |
| 91 | } |
lib/std/Build/Step/Compile.zig+96-1418| ... | ... | @@ -1,4 +1,5 @@ |
| 1 | 1 | const Compile = @This(); |
| 2 | ||
| 2 | 3 | const builtin = @import("builtin"); |
| 3 | 4 | |
| 4 | 5 | const std = @import("std"); |
| ... | ... | @@ -8,19 +9,15 @@ const fs = std.fs; |
| 8 | 9 | const assert = std.debug.assert; |
| 9 | 10 | const panic = std.debug.panic; |
| 10 | 11 | const StringHashMap = std.StringHashMap; |
| 11 | const Sha256 = std.crypto.hash.sha2.Sha256; | |
| 12 | 12 | const Allocator = std.mem.Allocator; |
| 13 | 13 | const Step = std.Build.Step; |
| 14 | 14 | const LazyPath = std.Build.LazyPath; |
| 15 | const PkgConfigPkg = std.Build.PkgConfigPkg; | |
| 16 | const PkgConfigError = std.Build.PkgConfigError; | |
| 17 | const RunError = std.Build.RunError; | |
| 18 | 15 | const Module = std.Build.Module; |
| 19 | 16 | const InstallDir = std.Build.InstallDir; |
| 20 | const GeneratedFile = std.Build.GeneratedFile; | |
| 21 | 17 | const Path = std.Build.Cache.Path; |
| 18 | const Configuration = std.Build.Configuration; | |
| 22 | 19 | |
| 23 | pub const base_id: Step.Id = .compile; | |
| 20 | pub const base_tag: Step.Tag = .compile; | |
| 24 | 21 | |
| 25 | 22 | step: Step, |
| 26 | 23 | root_module: *Module, |
| ... | ... | @@ -28,13 +25,11 @@ root_module: *Module, |
| 28 | 25 | name: []const u8, |
| 29 | 26 | linker_script: ?LazyPath = null, |
| 30 | 27 | version_script: ?LazyPath = null, |
| 28 | /// Deprecated. | |
| 31 | 29 | out_filename: []const u8, |
| 32 | out_lib_filename: []const u8, | |
| 33 | 30 | linkage: ?std.builtin.LinkMode = null, |
| 34 | 31 | version: ?std.SemanticVersion, |
| 35 | 32 | kind: Kind, |
| 36 | major_only_filename: ?[]const u8, | |
| 37 | name_only_filename: ?[]const u8, | |
| 38 | 33 | formatted_panics: ?bool = null, |
| 39 | 34 | compress_debug_sections: std.zig.CompressDebugSections = .none, |
| 40 | 35 | verbose_link: bool, |
| ... | ... | @@ -47,6 +42,7 @@ export_memory: bool = false, |
| 47 | 42 | /// For WebAssembly targets, this will allow for undefined symbols to |
| 48 | 43 | /// be imported from the host environment. |
| 49 | 44 | import_symbols: bool = false, |
| 45 | /// (WebAssembly) import function table from the host environment | |
| 50 | 46 | import_table: bool = false, |
| 51 | 47 | export_table: bool = false, |
| 52 | 48 | initial_memory: ?u64 = null, |
| ... | ... | @@ -60,7 +56,7 @@ filters: []const []const u8, |
| 60 | 56 | test_runner: ?TestRunner, |
| 61 | 57 | wasi_exec_model: ?std.builtin.WasiExecModel = null, |
| 62 | 58 | |
| 63 | installed_headers: std.array_list.Managed(HeaderInstallation), | |
| 59 | installed_headers: std.ArrayList(HeaderInstallation), | |
| 64 | 60 | |
| 65 | 61 | /// This step is used to create an include tree that dependent modules can add to their include |
| 66 | 62 | /// search paths. Installed headers are copied to this step. |
| ... | ... | @@ -83,8 +79,6 @@ win32_manifest: ?LazyPath = null, |
| 83 | 79 | /// Set via options; intended to be read-only after that. |
| 84 | 80 | win32_module_definition: ?LazyPath = null, |
| 85 | 81 | |
| 86 | installed_path: ?[]const u8, | |
| 87 | ||
| 88 | 82 | /// Base address for an executable image. |
| 89 | 83 | image_base: ?u64 = null, |
| 90 | 84 | |
| ... | ... | @@ -93,9 +87,13 @@ libc_file: ?LazyPath = null, |
| 93 | 87 | each_lib_rpath: ?bool = null, |
| 94 | 88 | /// On ELF targets, this will emit a link section called ".note.gnu.build-id" |
| 95 | 89 | /// which can be used to coordinate a stripped binary with its debug symbols. |
| 90 | /// | |
| 96 | 91 | /// As an example, the bloaty project refuses to work unless its inputs have |
| 97 | 92 | /// build ids, in order to prevent accidental mismatches. |
| 93 | /// | |
| 98 | 94 | /// The default is to not include this section because it slows down linking. |
| 95 | /// | |
| 96 | /// This option overrides the CLI argument passed to `zig build`. | |
| 99 | 97 | build_id: ?std.zig.BuildId = null, |
| 100 | 98 | |
| 101 | 99 | /// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF |
| ... | ... | @@ -147,8 +145,8 @@ link_z_defs: bool = false, |
| 147 | 145 | /// (Darwin) Install name for the dylib |
| 148 | 146 | install_name: ?[]const u8 = null, |
| 149 | 147 | |
| 150 | /// (Darwin) Path to entitlements file | |
| 151 | entitlements: ?[]const u8 = null, | |
| 148 | /// Must be passed in via `Options`. | |
| 149 | entitlements: ?LazyPath = null, | |
| 152 | 150 | |
| 153 | 151 | /// (Darwin) Size of the pagezero segment. |
| 154 | 152 | pagezero_size: ?u64 = null, |
| ... | ... | @@ -189,7 +187,7 @@ entry: Entry = .default, |
| 189 | 187 | /// List of symbols forced as undefined in the symbol table |
| 190 | 188 | /// thus forcing their resolution by the linker. |
| 191 | 189 | /// Corresponds to `-u <symbol>` for ELF/MachO and `/include:<symbol>` for COFF/PE. |
| 192 | force_undefined_symbols: std.StringHashMap(void), | |
| 190 | force_undefined_symbols: std.StringArrayHashMapUnmanaged(void), | |
| 193 | 191 | |
| 194 | 192 | /// Overrides the default stack size |
| 195 | 193 | stack_size: ?u64 = null, |
| ... | ... | @@ -213,21 +211,8 @@ allow_so_scripts: ?bool = null, |
| 213 | 211 | /// otherwise. |
| 214 | 212 | expect_errors: ?ExpectedCompileErrors = null, |
| 215 | 213 | |
| 216 | emit_directory: ?*GeneratedFile, | |
| 217 | ||
| 218 | generated_docs: ?*GeneratedFile, | |
| 219 | generated_asm: ?*GeneratedFile, | |
| 220 | generated_bin: ?*GeneratedFile, | |
| 221 | generated_pdb: ?*GeneratedFile, | |
| 222 | // hack for stage2_x86_64 + coff | |
| 223 | generated_compiler_rt_dyn_lib: ?*GeneratedFile, | |
| 224 | generated_implib: ?*GeneratedFile, | |
| 225 | generated_llvm_bc: ?*GeneratedFile, | |
| 226 | generated_llvm_ir: ?*GeneratedFile, | |
| 227 | generated_h: ?*GeneratedFile, | |
| 228 | ||
| 229 | /// The maximum number of distinct errors within a compilation step | |
| 230 | /// Defaults to `std.math.maxInt(u16)` | |
| 214 | /// The maximum number of distinct errors within a compilation step Defaults to | |
| 215 | /// `std.math.maxInt(u16)`. Overrides the argument passed to `zig build`. | |
| 231 | 216 | error_limit: ?u32 = null, |
| 232 | 217 | |
| 233 | 218 | /// Computed during make(). |
| ... | ... | @@ -235,10 +220,6 @@ is_linking_libc: bool = false, |
| 235 | 220 | /// Computed during make(). |
| 236 | 221 | is_linking_libcpp: bool = false, |
| 237 | 222 | |
| 238 | /// Populated during the make phase when there is a long-lived compiler process. | |
| 239 | /// Managed by the build runner, not user build script. | |
| 240 | zig_process: ?*Step.ZigProcess, | |
| 241 | ||
| 242 | 223 | /// Enables coverage instrumentation that is only useful if you are using third |
| 243 | 224 | /// party fuzzers that depend on it. Otherwise, slows down the instrumented |
| 244 | 225 | /// binary with unnecessary function calls. |
| ... | ... | @@ -253,6 +234,16 @@ zig_process: ?*Step.ZigProcess, |
| 253 | 234 | /// builtin fuzzer, see the `fuzz` flag in `Module`. |
| 254 | 235 | sanitize_coverage_trace_pc_guard: ?bool = null, |
| 255 | 236 | |
| 237 | emit_directory: Configuration.OptionalGeneratedFileIndex = .none, | |
| 238 | generated_docs: Configuration.OptionalGeneratedFileIndex = .none, | |
| 239 | generated_asm: Configuration.OptionalGeneratedFileIndex = .none, | |
| 240 | generated_bin: Configuration.OptionalGeneratedFileIndex = .none, | |
| 241 | generated_pdb: Configuration.OptionalGeneratedFileIndex = .none, | |
| 242 | generated_implib: Configuration.OptionalGeneratedFileIndex = .none, | |
| 243 | generated_llvm_bc: Configuration.OptionalGeneratedFileIndex = .none, | |
| 244 | generated_llvm_ir: Configuration.OptionalGeneratedFileIndex = .none, | |
| 245 | generated_h: Configuration.OptionalGeneratedFileIndex = .none, | |
| 246 | ||
| 256 | 247 | pub const ExpectedCompileErrors = union(enum) { |
| 257 | 248 | contains: []const u8, |
| 258 | 249 | exact: []const []const u8, |
| ... | ... | @@ -292,22 +283,11 @@ pub const Options = struct { |
| 292 | 283 | win32_manifest: ?LazyPath = null, |
| 293 | 284 | /// Win32 module definition file. |
| 294 | 285 | win32_module_definition: ?LazyPath = null, |
| 286 | /// (Darwin) Path to entitlements file | |
| 287 | entitlements: ?LazyPath = null, | |
| 295 | 288 | }; |
| 296 | 289 | |
| 297 | pub const Kind = enum { | |
| 298 | exe, | |
| 299 | lib, | |
| 300 | obj, | |
| 301 | @"test", | |
| 302 | test_obj, | |
| 303 | ||
| 304 | pub fn isTest(kind: Kind) bool { | |
| 305 | return switch (kind) { | |
| 306 | .exe, .lib, .obj => false, | |
| 307 | .@"test", .test_obj => true, | |
| 308 | }; | |
| 309 | } | |
| 310 | }; | |
| 290 | pub const Kind = Configuration.Step.Compile.Kind; | |
| 311 | 291 | |
| 312 | 292 | pub const HeaderInstallation = union(enum) { |
| 313 | 293 | file: File, |
| ... | ... | @@ -317,10 +297,10 @@ pub const HeaderInstallation = union(enum) { |
| 317 | 297 | source: LazyPath, |
| 318 | 298 | dest_rel_path: []const u8, |
| 319 | 299 | |
| 320 | pub fn dupe(file: File, b: *std.Build) File { | |
| 300 | pub fn dupe(file: File, graph: *const std.Build.Graph) File { | |
| 321 | 301 | return .{ |
| 322 | .source = file.source.dupe(b), | |
| 323 | .dest_rel_path = b.dupePath(file.dest_rel_path), | |
| 302 | .source = file.source.dupe(graph), | |
| 303 | .dest_rel_path = graph.dupePath(file.dest_rel_path), | |
| 324 | 304 | }; |
| 325 | 305 | } |
| 326 | 306 | }; |
| ... | ... | @@ -338,19 +318,19 @@ pub const HeaderInstallation = union(enum) { |
| 338 | 318 | /// `exclude_extensions` takes precedence over `include_extensions`. |
| 339 | 319 | include_extensions: ?[]const []const u8 = &.{".h"}, |
| 340 | 320 | |
| 341 | pub fn dupe(opts: Directory.Options, b: *std.Build) Directory.Options { | |
| 321 | pub fn dupe(opts: Directory.Options, graph: *const std.Build.Graph) Directory.Options { | |
| 342 | 322 | return .{ |
| 343 | .exclude_extensions = b.dupeStrings(opts.exclude_extensions), | |
| 344 | .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null, | |
| 323 | .exclude_extensions = graph.dupeStrings(opts.exclude_extensions), | |
| 324 | .include_extensions = if (opts.include_extensions) |incs| graph.dupeStrings(incs) else null, | |
| 345 | 325 | }; |
| 346 | 326 | } |
| 347 | 327 | }; |
| 348 | 328 | |
| 349 | pub fn dupe(dir: Directory, b: *std.Build) Directory { | |
| 329 | pub fn dupe(dir: Directory, graph: *const std.Build.Graph) Directory { | |
| 350 | 330 | return .{ |
| 351 | .source = dir.source.dupe(b), | |
| 352 | .dest_rel_path = b.dupePath(dir.dest_rel_path), | |
| 353 | .options = dir.options.dupe(b), | |
| 331 | .source = dir.source.dupe(graph), | |
| 332 | .dest_rel_path = graph.dupePath(dir.dest_rel_path), | |
| 333 | .options = dir.options.dupe(graph), | |
| 354 | 334 | }; |
| 355 | 335 | } |
| 356 | 336 | }; |
| ... | ... | @@ -361,10 +341,10 @@ pub const HeaderInstallation = union(enum) { |
| 361 | 341 | }; |
| 362 | 342 | } |
| 363 | 343 | |
| 364 | pub fn dupe(installation: HeaderInstallation, b: *std.Build) HeaderInstallation { | |
| 344 | pub fn dupe(installation: HeaderInstallation, graph: *const std.Build.Graph) HeaderInstallation { | |
| 365 | 345 | return switch (installation) { |
| 366 | .file => |f| .{ .file = f.dupe(b) }, | |
| 367 | .directory => |d| .{ .directory = d.dupe(b) }, | |
| 346 | .file => |f| .{ .file = f.dupe(graph) }, | |
| 347 | .directory => |d| .{ .directory = d.dupe(graph) }, | |
| 368 | 348 | }; |
| 369 | 349 | } |
| 370 | 350 | }; |
| ... | ... | @@ -378,6 +358,9 @@ pub const TestRunner = struct { |
| 378 | 358 | }; |
| 379 | 359 | |
| 380 | 360 | pub fn create(owner: *std.Build, options: Options) *Compile { |
| 361 | const graph = owner.graph; | |
| 362 | const arena = graph.arena; | |
| 363 | ||
| 381 | 364 | const name = owner.dupe(options.name); |
| 382 | 365 | if (mem.find(u8, name, "/") != null or mem.find(u8, name, "\\") != null) { |
| 383 | 366 | panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); |
| ... | ... | @@ -392,14 +375,17 @@ pub fn create(owner: *std.Build, options: Options) *Compile { |
| 392 | 375 | if (options.kind.isTest() and mem.eql(u8, name, "test")) |
| 393 | 376 | @tagName(options.kind) |
| 394 | 377 | else |
| 395 | owner.fmt("{s} {s}", .{ @tagName(options.kind), name }), | |
| 378 | owner.fmt("{t} {s}", .{ options.kind, name }), | |
| 396 | 379 | @tagName(options.root_module.optimize orelse .Debug), |
| 397 | resolved_target.query.zigTriple(owner.allocator) catch @panic("OOM"), | |
| 380 | resolved_target.query.zigTriple(arena) catch @panic("OOM"), | |
| 398 | 381 | }); |
| 399 | 382 | |
| 400 | const out_filename = std.zig.binNameAlloc(owner.allocator, .{ | |
| 383 | const out_filename = std.zig.binNameAlloc(arena, .{ | |
| 401 | 384 | .root_name = name, |
| 402 | .target = target, | |
| 385 | .cpu_arch = target.cpu.arch, | |
| 386 | .os_tag = target.os.tag, | |
| 387 | .ofmt = target.ofmt, | |
| 388 | .abi = target.abi, | |
| 403 | 389 | .output_mode = switch (options.kind) { |
| 404 | 390 | .lib => .Lib, |
| 405 | 391 | .obj, .test_obj => .Obj, |
| ... | ... | @@ -409,7 +395,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile { |
| 409 | 395 | .version = options.version, |
| 410 | 396 | }) catch @panic("OOM"); |
| 411 | 397 | |
| 412 | const compile = owner.allocator.create(Compile) catch @panic("OOM"); | |
| 398 | const compile = arena.create(Compile) catch @panic("OOM"); | |
| 413 | 399 | compile.* = .{ |
| 414 | 400 | .root_module = options.root_module, |
| 415 | 401 | .verbose_link = false, |
| ... | ... | @@ -418,52 +404,34 @@ pub fn create(owner: *std.Build, options: Options) *Compile { |
| 418 | 404 | .kind = options.kind, |
| 419 | 405 | .name = name, |
| 420 | 406 | .step = .init(.{ |
| 421 | .id = base_id, | |
| 407 | .tag = base_tag, | |
| 422 | 408 | .name = step_name, |
| 423 | 409 | .owner = owner, |
| 424 | .makeFn = make, | |
| 425 | 410 | .max_rss = options.max_rss, |
| 426 | 411 | }), |
| 427 | 412 | .version = options.version, |
| 428 | 413 | .out_filename = out_filename, |
| 429 | .out_lib_filename = undefined, | |
| 430 | .major_only_filename = null, | |
| 431 | .name_only_filename = null, | |
| 432 | .installed_headers = std.array_list.Managed(HeaderInstallation).init(owner.allocator), | |
| 414 | .installed_headers = .empty, | |
| 433 | 415 | .zig_lib_dir = null, |
| 434 | 416 | .exec_cmd_args = null, |
| 435 | 417 | .filters = options.filters, |
| 436 | 418 | .test_runner = null, // set below |
| 437 | 419 | .rdynamic = false, |
| 438 | .installed_path = null, | |
| 439 | .force_undefined_symbols = StringHashMap(void).init(owner.allocator), | |
| 440 | ||
| 441 | .emit_directory = null, | |
| 442 | .generated_docs = null, | |
| 443 | .generated_asm = null, | |
| 444 | .generated_bin = null, | |
| 445 | .generated_pdb = null, | |
| 446 | .generated_compiler_rt_dyn_lib = null, | |
| 447 | .generated_implib = null, | |
| 448 | .generated_llvm_bc = null, | |
| 449 | .generated_llvm_ir = null, | |
| 450 | .generated_h = null, | |
| 420 | .force_undefined_symbols = .empty, | |
| 451 | 421 | |
| 452 | 422 | .use_llvm = options.use_llvm, |
| 453 | 423 | .use_lld = options.use_lld, |
| 454 | 424 | .use_new_linker = null, |
| 455 | ||
| 456 | .zig_process = null, | |
| 457 | 425 | }; |
| 458 | 426 | |
| 459 | 427 | if (options.zig_lib_dir) |lp| { |
| 460 | compile.zig_lib_dir = lp.dupe(compile.step.owner); | |
| 428 | compile.zig_lib_dir = lp.dupe(graph); | |
| 461 | 429 | lp.addStepDependencies(&compile.step); |
| 462 | 430 | } |
| 463 | 431 | |
| 464 | 432 | if (options.test_runner) |runner| { |
| 465 | 433 | compile.test_runner = .{ |
| 466 | .path = runner.path.dupe(compile.step.owner), | |
| 434 | .path = runner.path.dupe(graph), | |
| 467 | 435 | .mode = runner.mode, |
| 468 | 436 | }; |
| 469 | 437 | runner.path.addStepDependencies(&compile.step); |
| ... | ... | @@ -473,45 +441,21 @@ pub fn create(owner: *std.Build, options: Options) *Compile { |
| 473 | 441 | // gets embedded, so for any other target the manifest file is just ignored. |
| 474 | 442 | if (target.ofmt == .coff) { |
| 475 | 443 | if (options.win32_manifest) |lp| { |
| 476 | compile.win32_manifest = lp.dupe(compile.step.owner); | |
| 444 | compile.win32_manifest = lp.dupe(graph); | |
| 477 | 445 | lp.addStepDependencies(&compile.step); |
| 478 | 446 | } |
| 479 | 447 | if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic) { |
| 480 | 448 | // Building a Win32 DLL, check for win32 .def file. |
| 481 | 449 | if (options.win32_module_definition) |lp| { |
| 482 | compile.win32_module_definition = lp.dupe(compile.step.owner); | |
| 450 | compile.win32_module_definition = lp.dupe(graph); | |
| 483 | 451 | lp.addStepDependencies(&compile.step); |
| 484 | 452 | } |
| 485 | 453 | } |
| 486 | 454 | } |
| 487 | 455 | |
| 488 | if (compile.kind == .lib) { | |
| 489 | if (compile.linkage != null and compile.linkage.? == .static) { | |
| 490 | compile.out_lib_filename = compile.out_filename; | |
| 491 | } else if (compile.version) |version| { | |
| 492 | if (target.os.tag.isDarwin()) { | |
| 493 | compile.major_only_filename = owner.fmt("lib{s}.{d}.dylib", .{ | |
| 494 | compile.name, | |
| 495 | version.major, | |
| 496 | }); | |
| 497 | compile.name_only_filename = owner.fmt("lib{s}.dylib", .{compile.name}); | |
| 498 | compile.out_lib_filename = compile.out_filename; | |
| 499 | } else if (target.os.tag == .windows) { | |
| 500 | compile.out_lib_filename = owner.fmt("{s}.lib", .{compile.name}); | |
| 501 | } else { | |
| 502 | compile.major_only_filename = owner.fmt("lib{s}.so.{d}", .{ compile.name, version.major }); | |
| 503 | compile.name_only_filename = owner.fmt("lib{s}.so", .{compile.name}); | |
| 504 | compile.out_lib_filename = compile.out_filename; | |
| 505 | } | |
| 506 | } else { | |
| 507 | if (target.os.tag.isDarwin()) { | |
| 508 | compile.out_lib_filename = compile.out_filename; | |
| 509 | } else if (target.os.tag == .windows) { | |
| 510 | compile.out_lib_filename = owner.fmt("{s}.lib", .{compile.name}); | |
| 511 | } else { | |
| 512 | compile.out_lib_filename = compile.out_filename; | |
| 513 | } | |
| 514 | } | |
| 456 | if (options.entitlements) |lp| { | |
| 457 | compile.entitlements = lp.dupe(graph); | |
| 458 | lp.addStepDependencies(&compile.step); | |
| 515 | 459 | } |
| 516 | 460 | |
| 517 | 461 | return compile; |
| ... | ... | @@ -521,12 +465,13 @@ pub fn create(owner: *std.Build, options: Options) *Compile { |
| 521 | 465 | /// When a module links with this artifact, all headers marked for installation are added to that |
| 522 | 466 | /// module's include search path. |
| 523 | 467 | pub fn installHeader(cs: *Compile, source: LazyPath, dest_rel_path: []const u8) void { |
| 524 | const b = cs.step.owner; | |
| 468 | const graph = cs.step.owner.graph; | |
| 469 | const arena = graph.arena; | |
| 525 | 470 | const installation: HeaderInstallation = .{ .file = .{ |
| 526 | .source = source.dupe(b), | |
| 527 | .dest_rel_path = b.dupePath(dest_rel_path), | |
| 471 | .source = source.dupe(graph), | |
| 472 | .dest_rel_path = graph.dupePath(dest_rel_path), | |
| 528 | 473 | } }; |
| 529 | cs.installed_headers.append(installation) catch @panic("OOM"); | |
| 474 | cs.installed_headers.append(arena, installation) catch @panic("OOM"); | |
| 530 | 475 | cs.addHeaderInstallationToIncludeTree(installation); |
| 531 | 476 | installation.getSource().addStepDependencies(&cs.step); |
| 532 | 477 | } |
| ... | ... | @@ -540,13 +485,14 @@ pub fn installHeadersDirectory( |
| 540 | 485 | dest_rel_path: []const u8, |
| 541 | 486 | options: HeaderInstallation.Directory.Options, |
| 542 | 487 | ) void { |
| 543 | const b = cs.step.owner; | |
| 488 | const graph = cs.step.owner.graph; | |
| 489 | const arena = graph.arena; | |
| 544 | 490 | const installation: HeaderInstallation = .{ .directory = .{ |
| 545 | .source = source.dupe(b), | |
| 546 | .dest_rel_path = b.dupePath(dest_rel_path), | |
| 547 | .options = options.dupe(b), | |
| 491 | .source = source.dupe(graph), | |
| 492 | .dest_rel_path = graph.dupePath(dest_rel_path), | |
| 493 | .options = options.dupe(graph), | |
| 548 | 494 | } }; |
| 549 | cs.installed_headers.append(installation) catch @panic("OOM"); | |
| 495 | cs.installed_headers.append(arena, installation) catch @panic("OOM"); | |
| 550 | 496 | cs.addHeaderInstallationToIncludeTree(installation); |
| 551 | 497 | installation.getSource().addStepDependencies(&cs.step); |
| 552 | 498 | } |
| ... | ... | @@ -563,9 +509,11 @@ pub fn installConfigHeader(cs: *Compile, config_header: *Step.ConfigHeader) void |
| 563 | 509 | /// module's include search path. |
| 564 | 510 | pub fn installLibraryHeaders(cs: *Compile, lib: *Compile) void { |
| 565 | 511 | assert(lib.kind == .lib); |
| 512 | const graph = cs.step.owner.graph; | |
| 513 | const arena = graph.arena; | |
| 566 | 514 | for (lib.installed_headers.items) |installation| { |
| 567 | const installation_copy = installation.dupe(lib.step.owner); | |
| 568 | cs.installed_headers.append(installation_copy) catch @panic("OOM"); | |
| 515 | const installation_copy = installation.dupe(graph); | |
| 516 | cs.installed_headers.append(arena, installation_copy) catch @panic("OOM"); | |
| 569 | 517 | cs.addHeaderInstallationToIncludeTree(installation_copy); |
| 570 | 518 | installation_copy.getSource().addStepDependencies(&cs.step); |
| 571 | 519 | } |
| ... | ... | @@ -612,20 +560,21 @@ pub fn addObjCopy(cs: *Compile, options: Step.ObjCopy.Options) *Step.ObjCopy { |
| 612 | 560 | } |
| 613 | 561 | |
| 614 | 562 | pub fn setLinkerScript(compile: *Compile, source: LazyPath) void { |
| 615 | const b = compile.step.owner; | |
| 616 | compile.linker_script = source.dupe(b); | |
| 563 | const graph = compile.step.owner.graph; | |
| 564 | compile.linker_script = source.dupe(graph); | |
| 617 | 565 | source.addStepDependencies(&compile.step); |
| 618 | 566 | } |
| 619 | 567 | |
| 620 | 568 | pub fn setVersionScript(compile: *Compile, source: LazyPath) void { |
| 621 | const b = compile.step.owner; | |
| 622 | compile.version_script = source.dupe(b); | |
| 569 | const graph = compile.step.owner.graph; | |
| 570 | compile.version_script = source.dupe(graph); | |
| 623 | 571 | source.addStepDependencies(&compile.step); |
| 624 | 572 | } |
| 625 | 573 | |
| 626 | 574 | pub fn forceUndefinedSymbol(compile: *Compile, symbol_name: []const u8) void { |
| 627 | const b = compile.step.owner; | |
| 628 | compile.force_undefined_symbols.put(b.dupe(symbol_name), {}) catch @panic("OOM"); | |
| 575 | const graph = compile.step.owner.graph; | |
| 576 | const arena = graph.allocator; | |
| 577 | compile.force_undefined_symbols.put(arena, graph.dupeString(symbol_name), {}) catch @panic("OOM"); | |
| 629 | 578 | } |
| 630 | 579 | |
| 631 | 580 | /// Returns whether the library, executable, or object depends on a particular system library. |
| ... | ... | @@ -701,122 +650,6 @@ pub fn producesImplib(compile: *Compile) bool { |
| 701 | 650 | return compile.isDll(); |
| 702 | 651 | } |
| 703 | 652 | |
| 704 | const PkgConfigResult = struct { | |
| 705 | cflags: []const []const u8, | |
| 706 | libs: []const []const u8, | |
| 707 | }; | |
| 708 | ||
| 709 | /// Run pkg-config for the given library name and parse the output, returning the arguments | |
| 710 | /// that should be passed to zig to link the given library. | |
| 711 | pub fn runPkgConfig(step: *Step, lib_name: []const u8) !PkgConfigResult { | |
| 712 | const wl_rpath_prefix = "-Wl,-rpath,"; | |
| 713 | ||
| 714 | const b = step.owner; | |
| 715 | const pkg_name = match: { | |
| 716 | // First we have to map the library name to pkg config name. Unfortunately, | |
| 717 | // there are several examples where this is not straightforward: | |
| 718 | // -lSDL2 -> pkg-config sdl2 | |
| 719 | // -lgdk-3 -> pkg-config gdk-3.0 | |
| 720 | // -latk-1.0 -> pkg-config atk | |
| 721 | // -lpulse -> pkg-config libpulse | |
| 722 | const pkgs = try getPkgConfigList(b); | |
| 723 | ||
| 724 | // Exact match means instant winner. | |
| 725 | for (pkgs) |pkg| { | |
| 726 | if (mem.eql(u8, pkg.name, lib_name)) { | |
| 727 | break :match pkg.name; | |
| 728 | } | |
| 729 | } | |
| 730 | ||
| 731 | // Next we'll try ignoring case. | |
| 732 | for (pkgs) |pkg| { | |
| 733 | if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { | |
| 734 | break :match pkg.name; | |
| 735 | } | |
| 736 | } | |
| 737 | ||
| 738 | // Prefixed "lib" or suffixed ".0". | |
| 739 | for (pkgs) |pkg| { | |
| 740 | if (std.ascii.findIgnoreCase(pkg.name, lib_name)) |pos| { | |
| 741 | const prefix = pkg.name[0..pos]; | |
| 742 | const suffix = pkg.name[pos + lib_name.len ..]; | |
| 743 | if (prefix.len > 0 and !mem.eql(u8, prefix, "lib")) continue; | |
| 744 | if (suffix.len > 0 and !mem.eql(u8, suffix, ".0")) continue; | |
| 745 | break :match pkg.name; | |
| 746 | } | |
| 747 | } | |
| 748 | ||
| 749 | // Trimming "-1.0". | |
| 750 | if (mem.endsWith(u8, lib_name, "-1.0")) { | |
| 751 | const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; | |
| 752 | for (pkgs) |pkg| { | |
| 753 | if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { | |
| 754 | break :match pkg.name; | |
| 755 | } | |
| 756 | } | |
| 757 | } | |
| 758 | ||
| 759 | return error.PackageNotFound; | |
| 760 | }; | |
| 761 | ||
| 762 | var code: u8 = undefined; | |
| 763 | const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config"; | |
| 764 | const stdout = if (b.runAllowFail(&[_][]const u8{ | |
| 765 | pkg_config_exe, | |
| 766 | pkg_name, | |
| 767 | "--cflags", | |
| 768 | "--libs", | |
| 769 | }, &code, .ignore)) |stdout| stdout else |err| switch (err) { | |
| 770 | error.ProcessTerminated => return error.PkgConfigCrashed, | |
| 771 | error.ExecNotSupported => return error.PkgConfigFailed, | |
| 772 | error.ExitCodeFailure => return error.PkgConfigFailed, | |
| 773 | error.FileNotFound => return error.PkgConfigNotInstalled, | |
| 774 | else => return err, | |
| 775 | }; | |
| 776 | ||
| 777 | var zig_cflags: std.ArrayList([]const u8) = .empty; | |
| 778 | defer zig_cflags.deinit(b.allocator); | |
| 779 | var zig_libs: std.ArrayList([]const u8) = .empty; | |
| 780 | defer zig_libs.deinit(b.allocator); | |
| 781 | ||
| 782 | var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t"); | |
| 783 | while (arg_it.next()) |arg| { | |
| 784 | if (mem.eql(u8, arg, "-I")) { | |
| 785 | const dir = arg_it.next() orelse return error.PkgConfigInvalidOutput; | |
| 786 | try zig_cflags.appendSlice(b.allocator, &.{ "-I", dir }); | |
| 787 | } else if (mem.startsWith(u8, arg, "-I")) { | |
| 788 | try zig_cflags.append(b.allocator, arg); | |
| 789 | } else if (mem.eql(u8, arg, "-L")) { | |
| 790 | const dir = arg_it.next() orelse return error.PkgConfigInvalidOutput; | |
| 791 | try zig_libs.appendSlice(b.allocator, &.{ "-L", dir }); | |
| 792 | } else if (mem.startsWith(u8, arg, "-L")) { | |
| 793 | try zig_libs.append(b.allocator, arg); | |
| 794 | } else if (mem.eql(u8, arg, "-l")) { | |
| 795 | const lib = arg_it.next() orelse return error.PkgConfigInvalidOutput; | |
| 796 | try zig_libs.appendSlice(b.allocator, &.{ "-l", lib }); | |
| 797 | } else if (mem.startsWith(u8, arg, "-l")) { | |
| 798 | try zig_libs.append(b.allocator, arg); | |
| 799 | } else if (mem.eql(u8, arg, "-D")) { | |
| 800 | const macro = arg_it.next() orelse return error.PkgConfigInvalidOutput; | |
| 801 | try zig_cflags.appendSlice(b.allocator, &.{ "-D", macro }); | |
| 802 | } else if (mem.startsWith(u8, arg, "-D")) { | |
| 803 | try zig_cflags.append(b.allocator, arg); | |
| 804 | } else if (mem.startsWith(u8, arg, wl_rpath_prefix)) { | |
| 805 | try zig_cflags.appendSlice(b.allocator, &.{ "-rpath", arg[wl_rpath_prefix.len..] }); | |
| 806 | } else if (b.debug_pkg_config) { | |
| 807 | return step.fail("unknown pkg-config flag '{s}'", .{arg}); | |
| 808 | } | |
| 809 | } | |
| 810 | ||
| 811 | try zig_cflags.shrinkToLen(b.allocator); | |
| 812 | try zig_libs.shrinkToLen(b.allocator); | |
| 813 | ||
| 814 | return .{ | |
| 815 | .cflags = zig_cflags.toOwnedSliceAssert(), | |
| 816 | .libs = zig_libs.toOwnedSliceAssert(), | |
| 817 | }; | |
| 818 | } | |
| 819 | ||
| 820 | 653 | pub fn setVerboseLink(compile: *Compile, value: bool) void { |
| 821 | 654 | compile.verbose_link = value; |
| 822 | 655 | } |
| ... | ... | @@ -826,22 +659,21 @@ pub fn setVerboseCC(compile: *Compile, value: bool) void { |
| 826 | 659 | } |
| 827 | 660 | |
| 828 | 661 | pub fn setLibCFile(compile: *Compile, libc_file: ?LazyPath) void { |
| 829 | const b = compile.step.owner; | |
| 662 | const graph = compile.step.owner.graph; | |
| 830 | 663 | if (libc_file) |f| { |
| 831 | compile.libc_file = f.dupe(b); | |
| 664 | compile.libc_file = f.dupe(graph); | |
| 832 | 665 | f.addStepDependencies(&compile.step); |
| 833 | 666 | } else { |
| 834 | 667 | compile.libc_file = null; |
| 835 | 668 | } |
| 836 | 669 | } |
| 837 | 670 | |
| 838 | fn getEmittedFileGeneric(compile: *Compile, output_file: *?*GeneratedFile) LazyPath { | |
| 839 | if (output_file.*) |file| return .{ .generated = .{ .file = file } }; | |
| 840 | const arena = compile.step.owner.allocator; | |
| 841 | const generated_file = arena.create(GeneratedFile) catch @panic("OOM"); | |
| 842 | generated_file.* = .{ .step = &compile.step }; | |
| 843 | output_file.* = generated_file; | |
| 844 | return .{ .generated = .{ .file = generated_file } }; | |
| 671 | fn getEmittedFileGeneric(compile: *Compile, output_file: *Configuration.OptionalGeneratedFileIndex) LazyPath { | |
| 672 | if (output_file.unwrap()) |index| return .{ .generated = .{ .index = index } }; | |
| 673 | const graph = compile.step.owner.graph; | |
| 674 | const index = graph.addGeneratedFile(&compile.step); | |
| 675 | output_file.* = .init(index); | |
| 676 | return .{ .generated = .{ .index = index } }; | |
| 845 | 677 | } |
| 846 | 678 | |
| 847 | 679 | /// Returns the path to the directory that contains the emitted binary file. |
| ... | ... | @@ -905,1175 +737,21 @@ pub fn getEmittedLlvmBc(compile: *Compile) LazyPath { |
| 905 | 737 | } |
| 906 | 738 | |
| 907 | 739 | pub fn setExecCmd(compile: *Compile, args: []const ?[]const u8) void { |
| 908 | const b = compile.step.owner; | |
| 740 | const graph = compile.step.owner.graph; | |
| 741 | const arena = graph.arena; | |
| 909 | 742 | assert(compile.kind == .@"test"); |
| 910 | const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM"); | |
| 743 | const duped_args = arena.alloc(?[]u8, args.len) catch @panic("OOM"); | |
| 911 | 744 | for (args, 0..) |arg, i| { |
| 912 | duped_args[i] = if (arg) |a| b.dupe(a) else null; | |
| 745 | duped_args[i] = if (arg) |a| graph.dupeString(a) else null; | |
| 913 | 746 | } |
| 914 | 747 | compile.exec_cmd_args = duped_args; |
| 915 | 748 | } |
| 916 | 749 | |
| 917 | const CliNamedModules = struct { | |
| 918 | modules: std.AutoArrayHashMapUnmanaged(*Module, void), | |
| 919 | names: std.StringArrayHashMapUnmanaged(void), | |
| 920 | ||
| 921 | /// Traverse the whole dependency graph and give every module a unique | |
| 922 | /// name, ideally one named after what it's called somewhere in the graph. | |
| 923 | /// It will help here to have both a mapping from module to name and a set | |
| 924 | /// of all the currently-used names. | |
| 925 | fn init(arena: Allocator, root_module: *Module) Allocator.Error!CliNamedModules { | |
| 926 | var compile: CliNamedModules = .{ | |
| 927 | .modules = .{}, | |
| 928 | .names = .{}, | |
| 929 | }; | |
| 930 | const graph = root_module.getGraph(); | |
| 931 | { | |
| 932 | assert(graph.modules[0] == root_module); | |
| 933 | try compile.modules.put(arena, root_module, {}); | |
| 934 | try compile.names.put(arena, "root", {}); | |
| 935 | } | |
| 936 | for (graph.modules[1..], graph.names[1..]) |mod, orig_name| { | |
| 937 | var name = orig_name; | |
| 938 | var n: usize = 0; | |
| 939 | while (true) { | |
| 940 | const gop = try compile.names.getOrPut(arena, name); | |
| 941 | if (!gop.found_existing) { | |
| 942 | try compile.modules.putNoClobber(arena, mod, {}); | |
| 943 | break; | |
| 944 | } | |
| 945 | name = try std.fmt.allocPrint(arena, "{s}{d}", .{ orig_name, n }); | |
| 946 | n += 1; | |
| 947 | } | |
| 948 | } | |
| 949 | return compile; | |
| 950 | } | |
| 951 | }; | |
| 952 | ||
| 953 | fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) ![]const u8 { | |
| 954 | const step = &compile.step; | |
| 955 | const b = step.owner; | |
| 956 | const graph = b.graph; | |
| 957 | const io = graph.io; | |
| 958 | const maybe_path: ?*GeneratedFile = @field(compile, tag_name); | |
| 959 | ||
| 960 | const generated_file = maybe_path orelse { | |
| 961 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 962 | std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {}; | |
| 963 | io.unlockStderr(); | |
| 964 | @panic("missing emit option for " ++ tag_name); | |
| 965 | }; | |
| 966 | ||
| 967 | const path = generated_file.path orelse { | |
| 968 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 969 | std.Build.dumpBadGetPathHelp(&compile.step, stderr.terminal(), compile.step.owner, asking_step) catch {}; | |
| 970 | io.unlockStderr(); | |
| 971 | @panic(tag_name ++ " is null. Is there a missing step dependency?"); | |
| 972 | }; | |
| 973 | ||
| 974 | return path; | |
| 975 | } | |
| 976 | ||
| 977 | fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { | |
| 978 | const step = &compile.step; | |
| 979 | const b = step.owner; | |
| 980 | const arena = b.allocator; | |
| 981 | ||
| 982 | var zig_args = std.array_list.Managed([]const u8).init(arena); | |
| 983 | defer zig_args.deinit(); | |
| 984 | ||
| 985 | try zig_args.append(b.graph.zig_exe); | |
| 986 | ||
| 987 | const cmd = switch (compile.kind) { | |
| 988 | .lib => "build-lib", | |
| 989 | .exe => "build-exe", | |
| 990 | .obj => "build-obj", | |
| 991 | .@"test" => "test", | |
| 992 | .test_obj => "test-obj", | |
| 993 | }; | |
| 994 | try zig_args.append(cmd); | |
| 995 | ||
| 996 | if (b.reference_trace) |some| { | |
| 997 | try zig_args.append(try std.fmt.allocPrint(arena, "-freference-trace={d}", .{some})); | |
| 998 | } | |
| 999 | try addFlag(&zig_args, "allow-so-scripts", compile.allow_so_scripts orelse b.graph.allow_so_scripts); | |
| 1000 | ||
| 1001 | try addFlag(&zig_args, "llvm", compile.use_llvm); | |
| 1002 | try addFlag(&zig_args, "lld", compile.use_lld); | |
| 1003 | try addFlag(&zig_args, "new-linker", compile.use_new_linker); | |
| 1004 | ||
| 1005 | if (compile.root_module.resolved_target.?.query.ofmt) |ofmt| { | |
| 1006 | try zig_args.append(try std.fmt.allocPrint(arena, "-ofmt={s}", .{@tagName(ofmt)})); | |
| 1007 | } | |
| 1008 | ||
| 1009 | switch (compile.entry) { | |
| 1010 | .default => {}, | |
| 1011 | .disabled => try zig_args.append("-fno-entry"), | |
| 1012 | .enabled => try zig_args.append("-fentry"), | |
| 1013 | .symbol_name => |entry_name| { | |
| 1014 | try zig_args.append(try std.fmt.allocPrint(arena, "-fentry={s}", .{entry_name})); | |
| 1015 | }, | |
| 1016 | } | |
| 1017 | ||
| 1018 | { | |
| 1019 | var symbol_it = compile.force_undefined_symbols.keyIterator(); | |
| 1020 | while (symbol_it.next()) |symbol_name| { | |
| 1021 | try zig_args.append("--force_undefined"); | |
| 1022 | try zig_args.append(symbol_name.*); | |
| 1023 | } | |
| 1024 | } | |
| 1025 | ||
| 1026 | if (compile.stack_size) |stack_size| { | |
| 1027 | try zig_args.append("--stack"); | |
| 1028 | try zig_args.append(try std.fmt.allocPrint(arena, "{}", .{stack_size})); | |
| 1029 | } | |
| 1030 | ||
| 1031 | if (fuzz) { | |
| 1032 | try zig_args.append("-ffuzz"); | |
| 1033 | } | |
| 1034 | ||
| 1035 | { | |
| 1036 | // Stores system libraries that have already been seen for at least one | |
| 1037 | // module, along with any arguments that need to be passed to the | |
| 1038 | // compiler for each module individually. | |
| 1039 | var seen_system_libs: std.StringHashMapUnmanaged([]const []const u8) = .empty; | |
| 1040 | var frameworks: std.StringArrayHashMapUnmanaged(Module.LinkFrameworkOptions) = .empty; | |
| 1041 | ||
| 1042 | var prev_has_cflags = false; | |
| 1043 | var prev_has_rcflags = false; | |
| 1044 | var prev_search_strategy: Module.SystemLib.SearchStrategy = .paths_first; | |
| 1045 | var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic; | |
| 1046 | // Track the number of positional arguments so that a nice error can be | |
| 1047 | // emitted if there is nothing to link. | |
| 1048 | var total_linker_objects: usize = @intFromBool(compile.root_module.root_source_file != null); | |
| 1049 | ||
| 1050 | // Fully recursive iteration including dynamic libraries to detect | |
| 1051 | // libc and libc++ linkage. | |
| 1052 | for (compile.getCompileDependencies(true)) |some_compile| { | |
| 1053 | for (some_compile.root_module.getGraph().modules) |mod| { | |
| 1054 | if (mod.link_libc == true) compile.is_linking_libc = true; | |
| 1055 | if (mod.link_libcpp == true) compile.is_linking_libcpp = true; | |
| 1056 | } | |
| 1057 | } | |
| 1058 | ||
| 1059 | var cli_named_modules = try CliNamedModules.init(arena, compile.root_module); | |
| 1060 | ||
| 1061 | // For this loop, don't chase dynamic libraries because their link | |
| 1062 | // objects are already linked. | |
| 1063 | for (compile.getCompileDependencies(false)) |dep_compile| { | |
| 1064 | for (dep_compile.root_module.getGraph().modules) |mod| { | |
| 1065 | // While walking transitive dependencies, if a given link object is | |
| 1066 | // already included in a library, it should not redundantly be | |
| 1067 | // placed on the linker line of the dependee. | |
| 1068 | const my_responsibility = dep_compile == compile; | |
| 1069 | const already_linked = !my_responsibility and dep_compile.isDynamicLibrary(); | |
| 1070 | ||
| 1071 | // Inherit dependencies on darwin frameworks. | |
| 1072 | if (!already_linked) { | |
| 1073 | for (mod.frameworks.keys(), mod.frameworks.values()) |name, info| { | |
| 1074 | try frameworks.put(arena, name, info); | |
| 1075 | } | |
| 1076 | } | |
| 1077 | ||
| 1078 | // Inherit dependencies on system libraries and static libraries. | |
| 1079 | for (mod.link_objects.items) |link_object| { | |
| 1080 | switch (link_object) { | |
| 1081 | .static_path => |static_path| { | |
| 1082 | if (my_responsibility) { | |
| 1083 | try zig_args.append(static_path.getPath2(mod.owner, step)); | |
| 1084 | total_linker_objects += 1; | |
| 1085 | } | |
| 1086 | }, | |
| 1087 | .system_lib => |system_lib| { | |
| 1088 | const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name); | |
| 1089 | if (system_lib_gop.found_existing) { | |
| 1090 | try zig_args.appendSlice(system_lib_gop.value_ptr.*); | |
| 1091 | continue; | |
| 1092 | } else { | |
| 1093 | system_lib_gop.value_ptr.* = &.{}; | |
| 1094 | } | |
| 1095 | ||
| 1096 | if (already_linked) | |
| 1097 | continue; | |
| 1098 | ||
| 1099 | if ((system_lib.search_strategy != prev_search_strategy or | |
| 1100 | system_lib.preferred_link_mode != prev_preferred_link_mode) and | |
| 1101 | compile.linkage != .static) | |
| 1102 | { | |
| 1103 | switch (system_lib.search_strategy) { | |
| 1104 | .no_fallback => switch (system_lib.preferred_link_mode) { | |
| 1105 | .dynamic => try zig_args.append("-search_dylibs_only"), | |
| 1106 | .static => try zig_args.append("-search_static_only"), | |
| 1107 | }, | |
| 1108 | .paths_first => switch (system_lib.preferred_link_mode) { | |
| 1109 | .dynamic => try zig_args.append("-search_paths_first"), | |
| 1110 | .static => try zig_args.append("-search_paths_first_static"), | |
| 1111 | }, | |
| 1112 | .mode_first => switch (system_lib.preferred_link_mode) { | |
| 1113 | .dynamic => try zig_args.append("-search_dylibs_first"), | |
| 1114 | .static => try zig_args.append("-search_static_first"), | |
| 1115 | }, | |
| 1116 | } | |
| 1117 | prev_search_strategy = system_lib.search_strategy; | |
| 1118 | prev_preferred_link_mode = system_lib.preferred_link_mode; | |
| 1119 | } | |
| 1120 | ||
| 1121 | const prefix: []const u8 = prefix: { | |
| 1122 | if (system_lib.needed) break :prefix "-needed-l"; | |
| 1123 | if (system_lib.weak) break :prefix "-weak-l"; | |
| 1124 | break :prefix "-l"; | |
| 1125 | }; | |
| 1126 | switch (system_lib.use_pkg_config) { | |
| 1127 | .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), | |
| 1128 | .yes, .force => { | |
| 1129 | if (runPkgConfig(&compile.step, system_lib.name)) |result| { | |
| 1130 | try zig_args.appendSlice(result.cflags); | |
| 1131 | try zig_args.appendSlice(result.libs); | |
| 1132 | try seen_system_libs.put(arena, system_lib.name, result.cflags); | |
| 1133 | } else |err| switch (err) { | |
| 1134 | error.PkgConfigInvalidOutput, | |
| 1135 | error.PkgConfigCrashed, | |
| 1136 | error.PkgConfigFailed, | |
| 1137 | error.PkgConfigNotInstalled, | |
| 1138 | error.PackageNotFound, | |
| 1139 | => switch (system_lib.use_pkg_config) { | |
| 1140 | .yes => { | |
| 1141 | // pkg-config failed, so fall back to linking the library | |
| 1142 | // by name directly. | |
| 1143 | try zig_args.append(b.fmt("{s}{s}", .{ | |
| 1144 | prefix, | |
| 1145 | system_lib.name, | |
| 1146 | })); | |
| 1147 | }, | |
| 1148 | .force => { | |
| 1149 | panic("pkg-config failed for library {s}", .{system_lib.name}); | |
| 1150 | }, | |
| 1151 | .no => unreachable, | |
| 1152 | }, | |
| 1153 | ||
| 1154 | else => |e| return e, | |
| 1155 | } | |
| 1156 | }, | |
| 1157 | } | |
| 1158 | }, | |
| 1159 | .other_step => |other| { | |
| 1160 | switch (other.kind) { | |
| 1161 | .exe => return step.fail("cannot link with an executable build artifact", .{}), | |
| 1162 | .@"test" => return step.fail("cannot link with a test", .{}), | |
| 1163 | .obj, .test_obj => { | |
| 1164 | const included_in_lib_or_obj = !my_responsibility and | |
| 1165 | (dep_compile.kind == .lib or dep_compile.kind == .obj or dep_compile.kind == .test_obj); | |
| 1166 | if (!already_linked and !included_in_lib_or_obj) { | |
| 1167 | try zig_args.append(other.getEmittedBin().getPath2(b, step)); | |
| 1168 | total_linker_objects += 1; | |
| 1169 | } | |
| 1170 | }, | |
| 1171 | .lib => l: { | |
| 1172 | const other_produces_implib = other.producesImplib(); | |
| 1173 | const other_is_static = other_produces_implib or other.isStaticLibrary(); | |
| 1174 | ||
| 1175 | if (compile.isStaticLibrary() and other_is_static) { | |
| 1176 | // Avoid putting a static library inside a static library. | |
| 1177 | break :l; | |
| 1178 | } | |
| 1179 | ||
| 1180 | // For DLLs, we must link against the implib. | |
| 1181 | // For everything else, we directly link | |
| 1182 | // against the library file. | |
| 1183 | const full_path_lib = if (other_produces_implib) | |
| 1184 | try other.getGeneratedFilePath("generated_implib", &compile.step) | |
| 1185 | else | |
| 1186 | try other.getGeneratedFilePath("generated_bin", &compile.step); | |
| 1187 | ||
| 1188 | try zig_args.append(full_path_lib); | |
| 1189 | total_linker_objects += 1; | |
| 1190 | ||
| 1191 | if (other.linkage == .dynamic and | |
| 1192 | compile.rootModuleTarget().os.tag != .windows) | |
| 1193 | { | |
| 1194 | if (fs.path.dirname(full_path_lib)) |dirname| { | |
| 1195 | try zig_args.append("-rpath"); | |
| 1196 | try zig_args.append(dirname); | |
| 1197 | } | |
| 1198 | } | |
| 1199 | }, | |
| 1200 | } | |
| 1201 | }, | |
| 1202 | .assembly_file => |asm_file| l: { | |
| 1203 | if (!my_responsibility) break :l; | |
| 1204 | ||
| 1205 | if (prev_has_cflags) { | |
| 1206 | try zig_args.append("-cflags"); | |
| 1207 | try zig_args.append("--"); | |
| 1208 | prev_has_cflags = false; | |
| 1209 | } | |
| 1210 | try zig_args.append(asm_file.getPath2(mod.owner, step)); | |
| 1211 | total_linker_objects += 1; | |
| 1212 | }, | |
| 1213 | ||
| 1214 | .c_source_file => |c_source_file| l: { | |
| 1215 | if (!my_responsibility) break :l; | |
| 1216 | ||
| 1217 | if (prev_has_cflags or c_source_file.flags.len != 0) { | |
| 1218 | try zig_args.append("-cflags"); | |
| 1219 | for (c_source_file.flags) |arg| { | |
| 1220 | try zig_args.append(arg); | |
| 1221 | } | |
| 1222 | try zig_args.append("--"); | |
| 1223 | } | |
| 1224 | prev_has_cflags = (c_source_file.flags.len != 0); | |
| 1225 | ||
| 1226 | if (c_source_file.language) |lang| { | |
| 1227 | try zig_args.append("-x"); | |
| 1228 | try zig_args.append(lang.internalIdentifier()); | |
| 1229 | } | |
| 1230 | ||
| 1231 | try zig_args.append(c_source_file.file.getPath2(mod.owner, step)); | |
| 1232 | ||
| 1233 | if (c_source_file.language != null) { | |
| 1234 | try zig_args.append("-x"); | |
| 1235 | try zig_args.append("none"); | |
| 1236 | } | |
| 1237 | total_linker_objects += 1; | |
| 1238 | }, | |
| 1239 | ||
| 1240 | .c_source_files => |c_source_files| l: { | |
| 1241 | if (!my_responsibility) break :l; | |
| 1242 | ||
| 1243 | if (prev_has_cflags or c_source_files.flags.len != 0) { | |
| 1244 | try zig_args.append("-cflags"); | |
| 1245 | for (c_source_files.flags) |arg| { | |
| 1246 | try zig_args.append(arg); | |
| 1247 | } | |
| 1248 | try zig_args.append("--"); | |
| 1249 | } | |
| 1250 | prev_has_cflags = (c_source_files.flags.len != 0); | |
| 1251 | ||
| 1252 | if (c_source_files.language) |lang| { | |
| 1253 | try zig_args.append("-x"); | |
| 1254 | try zig_args.append(lang.internalIdentifier()); | |
| 1255 | } | |
| 1256 | ||
| 1257 | const root_path = c_source_files.root.getPath2(mod.owner, step); | |
| 1258 | for (c_source_files.files) |file| { | |
| 1259 | try zig_args.append(b.pathJoin(&.{ root_path, file })); | |
| 1260 | } | |
| 1261 | ||
| 1262 | if (c_source_files.language != null) { | |
| 1263 | try zig_args.append("-x"); | |
| 1264 | try zig_args.append("none"); | |
| 1265 | } | |
| 1266 | ||
| 1267 | total_linker_objects += c_source_files.files.len; | |
| 1268 | }, | |
| 1269 | ||
| 1270 | .win32_resource_file => |rc_source_file| l: { | |
| 1271 | if (!my_responsibility) break :l; | |
| 1272 | ||
| 1273 | if (rc_source_file.flags.len == 0 and rc_source_file.include_paths.len == 0) { | |
| 1274 | if (prev_has_rcflags) { | |
| 1275 | try zig_args.append("-rcflags"); | |
| 1276 | try zig_args.append("--"); | |
| 1277 | prev_has_rcflags = false; | |
| 1278 | } | |
| 1279 | } else { | |
| 1280 | try zig_args.append("-rcflags"); | |
| 1281 | for (rc_source_file.flags) |arg| { | |
| 1282 | try zig_args.append(arg); | |
| 1283 | } | |
| 1284 | for (rc_source_file.include_paths) |include_path| { | |
| 1285 | try zig_args.append("/I"); | |
| 1286 | try zig_args.append(include_path.getPath2(mod.owner, step)); | |
| 1287 | } | |
| 1288 | try zig_args.append("--"); | |
| 1289 | prev_has_rcflags = true; | |
| 1290 | } | |
| 1291 | try zig_args.append(rc_source_file.file.getPath2(mod.owner, step)); | |
| 1292 | total_linker_objects += 1; | |
| 1293 | }, | |
| 1294 | } | |
| 1295 | } | |
| 1296 | ||
| 1297 | // We need to emit the --mod argument here so that the above link objects | |
| 1298 | // have the correct parent module, but only if the module is part of | |
| 1299 | // this compilation. | |
| 1300 | if (!my_responsibility) continue; | |
| 1301 | if (cli_named_modules.modules.getIndex(mod)) |module_cli_index| { | |
| 1302 | const module_cli_name = cli_named_modules.names.keys()[module_cli_index]; | |
| 1303 | try mod.appendZigProcessFlags(&zig_args, step); | |
| 1304 | ||
| 1305 | // --dep arguments | |
| 1306 | try zig_args.ensureUnusedCapacity(mod.import_table.count() * 2); | |
| 1307 | for (mod.import_table.keys(), mod.import_table.values()) |name, import| { | |
| 1308 | const import_index = cli_named_modules.modules.getIndex(import).?; | |
| 1309 | const import_cli_name = cli_named_modules.names.keys()[import_index]; | |
| 1310 | zig_args.appendAssumeCapacity("--dep"); | |
| 1311 | if (std.mem.eql(u8, import_cli_name, name)) { | |
| 1312 | zig_args.appendAssumeCapacity(import_cli_name); | |
| 1313 | } else { | |
| 1314 | zig_args.appendAssumeCapacity(b.fmt("{s}={s}", .{ name, import_cli_name })); | |
| 1315 | } | |
| 1316 | } | |
| 1317 | ||
| 1318 | // When the CLI sees a -M argument, it determines whether it | |
| 1319 | // implies the existence of a Zig compilation unit based on | |
| 1320 | // whether there is a root source file. If there is no root | |
| 1321 | // source file, then this is not a zig compilation unit - it is | |
| 1322 | // perhaps a set of linker objects, or C source files instead. | |
| 1323 | // Linker objects are added to the CLI globally, while C source | |
| 1324 | // files must have a module parent. | |
| 1325 | if (mod.root_source_file) |lp| { | |
| 1326 | const src = lp.getPath2(mod.owner, step); | |
| 1327 | try zig_args.append(b.fmt("-M{s}={s}", .{ module_cli_name, src })); | |
| 1328 | } else if (moduleNeedsCliArg(mod)) { | |
| 1329 | try zig_args.append(b.fmt("-M{s}", .{module_cli_name})); | |
| 1330 | } | |
| 1331 | } | |
| 1332 | } | |
| 1333 | } | |
| 1334 | ||
| 1335 | if (total_linker_objects == 0) { | |
| 1336 | return step.fail("the linker needs one or more objects to link", .{}); | |
| 1337 | } | |
| 1338 | ||
| 1339 | for (frameworks.keys(), frameworks.values()) |name, info| { | |
| 1340 | if (info.needed) { | |
| 1341 | try zig_args.append("-needed_framework"); | |
| 1342 | } else if (info.weak) { | |
| 1343 | try zig_args.append("-weak_framework"); | |
| 1344 | } else { | |
| 1345 | try zig_args.append("-framework"); | |
| 1346 | } | |
| 1347 | try zig_args.append(name); | |
| 1348 | } | |
| 1349 | ||
| 1350 | if (compile.is_linking_libcpp) { | |
| 1351 | try zig_args.append("-lc++"); | |
| 1352 | } | |
| 1353 | ||
| 1354 | if (compile.is_linking_libc) { | |
| 1355 | try zig_args.append("-lc"); | |
| 1356 | } | |
| 1357 | } | |
| 1358 | ||
| 1359 | if (compile.win32_manifest) |manifest_file| { | |
| 1360 | try zig_args.append(manifest_file.getPath2(b, step)); | |
| 1361 | } | |
| 1362 | ||
| 1363 | if (compile.win32_module_definition) |module_file| { | |
| 1364 | try zig_args.append(module_file.getPath2(b, step)); | |
| 1365 | } | |
| 1366 | ||
| 1367 | if (compile.image_base) |image_base| { | |
| 1368 | try zig_args.append("--image-base"); | |
| 1369 | try zig_args.append(b.fmt("0x{x}", .{image_base})); | |
| 1370 | } | |
| 1371 | ||
| 1372 | for (compile.filters) |filter| { | |
| 1373 | try zig_args.append("--test-filter"); | |
| 1374 | try zig_args.append(filter); | |
| 1375 | } | |
| 1376 | ||
| 1377 | if (compile.test_runner) |test_runner| { | |
| 1378 | try zig_args.append("--test-runner"); | |
| 1379 | try zig_args.append(test_runner.path.getPath2(b, step)); | |
| 1380 | } | |
| 1381 | ||
| 1382 | for (b.debug_log_scopes) |log_scope| { | |
| 1383 | try zig_args.append("--debug-log"); | |
| 1384 | try zig_args.append(log_scope); | |
| 1385 | } | |
| 1386 | ||
| 1387 | if (b.debug_compile_errors) { | |
| 1388 | try zig_args.append("--debug-compile-errors"); | |
| 1389 | } | |
| 1390 | ||
| 1391 | if (b.debug_incremental) { | |
| 1392 | try zig_args.append("--debug-incremental"); | |
| 1393 | } | |
| 1394 | ||
| 1395 | if (b.verbose_air) try zig_args.append("--verbose-air"); | |
| 1396 | if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path})); | |
| 1397 | if (b.verbose_llvm_bc) |path| try zig_args.append(b.fmt("--verbose-llvm-bc={s}", .{path})); | |
| 1398 | if (b.verbose_link or compile.verbose_link) try zig_args.append("--verbose-link"); | |
| 1399 | if (b.verbose_cc or compile.verbose_cc) try zig_args.append("--verbose-cc"); | |
| 1400 | if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features"); | |
| 1401 | if (b.graph.time_report) try zig_args.append("--time-report"); | |
| 1402 | ||
| 1403 | if (compile.generated_asm != null) try zig_args.append("-femit-asm"); | |
| 1404 | if (compile.generated_bin == null) try zig_args.append("-fno-emit-bin"); | |
| 1405 | if (compile.generated_docs != null) try zig_args.append("-femit-docs"); | |
| 1406 | if (compile.generated_implib != null) try zig_args.append("-femit-implib"); | |
| 1407 | if (compile.generated_llvm_bc != null) try zig_args.append("-femit-llvm-bc"); | |
| 1408 | if (compile.generated_llvm_ir != null) try zig_args.append("-femit-llvm-ir"); | |
| 1409 | if (compile.generated_h != null) try zig_args.append("-femit-h"); | |
| 1410 | ||
| 1411 | try addFlag(&zig_args, "formatted-panics", compile.formatted_panics); | |
| 1412 | ||
| 1413 | switch (compile.compress_debug_sections) { | |
| 1414 | .none => {}, | |
| 1415 | .zlib => try zig_args.append("--compress-debug-sections=zlib"), | |
| 1416 | .zstd => try zig_args.append("--compress-debug-sections=zstd"), | |
| 1417 | } | |
| 1418 | ||
| 1419 | if (compile.link_eh_frame_hdr) { | |
| 1420 | try zig_args.append("--eh-frame-hdr"); | |
| 1421 | } | |
| 1422 | if (compile.link_emit_relocs) { | |
| 1423 | try zig_args.append("--emit-relocs"); | |
| 1424 | } | |
| 1425 | if (compile.link_function_sections) { | |
| 1426 | try zig_args.append("-ffunction-sections"); | |
| 1427 | } | |
| 1428 | if (compile.link_data_sections) { | |
| 1429 | try zig_args.append("-fdata-sections"); | |
| 1430 | } | |
| 1431 | if (compile.link_gc_sections) |x| { | |
| 1432 | try zig_args.append(if (x) "--gc-sections" else "--no-gc-sections"); | |
| 1433 | } | |
| 1434 | if (!compile.linker_dynamicbase) { | |
| 1435 | try zig_args.append("--no-dynamicbase"); | |
| 1436 | } | |
| 1437 | if (compile.linker_allow_shlib_undefined) |x| { | |
| 1438 | try zig_args.append(if (x) "-fallow-shlib-undefined" else "-fno-allow-shlib-undefined"); | |
| 1439 | } | |
| 1440 | if (compile.link_z_notext) { | |
| 1441 | try zig_args.append("-z"); | |
| 1442 | try zig_args.append("notext"); | |
| 1443 | } | |
| 1444 | if (!compile.link_z_relro) { | |
| 1445 | try zig_args.append("-z"); | |
| 1446 | try zig_args.append("norelro"); | |
| 1447 | } | |
| 1448 | if (compile.link_z_lazy) { | |
| 1449 | try zig_args.append("-z"); | |
| 1450 | try zig_args.append("lazy"); | |
| 1451 | } | |
| 1452 | if (compile.link_z_common_page_size) |size| { | |
| 1453 | try zig_args.append("-z"); | |
| 1454 | try zig_args.append(b.fmt("common-page-size={d}", .{size})); | |
| 1455 | } | |
| 1456 | if (compile.link_z_max_page_size) |size| { | |
| 1457 | try zig_args.append("-z"); | |
| 1458 | try zig_args.append(b.fmt("max-page-size={d}", .{size})); | |
| 1459 | } | |
| 1460 | if (compile.link_z_defs) { | |
| 1461 | try zig_args.append("-z"); | |
| 1462 | try zig_args.append("defs"); | |
| 1463 | } | |
| 1464 | ||
| 1465 | if (compile.libc_file) |libc_file| { | |
| 1466 | try zig_args.append("--libc"); | |
| 1467 | try zig_args.append(libc_file.getPath2(b, step)); | |
| 1468 | } else if (b.libc_file) |libc_file| { | |
| 1469 | try zig_args.append("--libc"); | |
| 1470 | try zig_args.append(libc_file); | |
| 1471 | } | |
| 1472 | ||
| 1473 | try zig_args.append("--cache-dir"); | |
| 1474 | try zig_args.append(b.cache_root.path orelse "."); | |
| 1475 | ||
| 1476 | try zig_args.append("--global-cache-dir"); | |
| 1477 | try zig_args.append(b.graph.global_cache_root.path orelse "."); | |
| 1478 | ||
| 1479 | if (b.graph.debug_compiler_runtime_libs) |mode| | |
| 1480 | try zig_args.append(b.fmt("--debug-rt={t}", .{mode})); | |
| 1481 | ||
| 1482 | try zig_args.append("--name"); | |
| 1483 | try zig_args.append(compile.name); | |
| 1484 | ||
| 1485 | if (compile.linkage) |some| switch (some) { | |
| 1486 | .dynamic => try zig_args.append("-dynamic"), | |
| 1487 | .static => try zig_args.append("-static"), | |
| 1488 | }; | |
| 1489 | if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic) { | |
| 1490 | if (compile.version) |version| { | |
| 1491 | try zig_args.append("--version"); | |
| 1492 | try zig_args.append(b.fmt("{f}", .{version})); | |
| 1493 | } | |
| 1494 | ||
| 1495 | if (compile.rootModuleTarget().os.tag.isDarwin()) { | |
| 1496 | const install_name = compile.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{ | |
| 1497 | compile.rootModuleTarget().libPrefix(), | |
| 1498 | compile.name, | |
| 1499 | compile.rootModuleTarget().dynamicLibSuffix(), | |
| 1500 | }); | |
| 1501 | try zig_args.append("-install_name"); | |
| 1502 | try zig_args.append(install_name); | |
| 1503 | } | |
| 1504 | } | |
| 1505 | ||
| 1506 | if (compile.entitlements) |entitlements| { | |
| 1507 | try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); | |
| 1508 | } | |
| 1509 | if (compile.pagezero_size) |pagezero_size| { | |
| 1510 | const size = try std.fmt.allocPrint(arena, "{x}", .{pagezero_size}); | |
| 1511 | try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); | |
| 1512 | } | |
| 1513 | if (compile.headerpad_size) |headerpad_size| { | |
| 1514 | const size = try std.fmt.allocPrint(arena, "{x}", .{headerpad_size}); | |
| 1515 | try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); | |
| 1516 | } | |
| 1517 | if (compile.headerpad_max_install_names) { | |
| 1518 | try zig_args.append("-headerpad_max_install_names"); | |
| 1519 | } | |
| 1520 | if (compile.dead_strip_dylibs) { | |
| 1521 | try zig_args.append("-dead_strip_dylibs"); | |
| 1522 | } | |
| 1523 | if (compile.force_load_objc) { | |
| 1524 | try zig_args.append("-ObjC"); | |
| 1525 | } | |
| 1526 | if (compile.discard_local_symbols) { | |
| 1527 | try zig_args.append("--discard-all"); | |
| 1528 | } | |
| 1529 | ||
| 1530 | try addFlag(&zig_args, "compiler-rt", compile.bundle_compiler_rt); | |
| 1531 | try addFlag(&zig_args, "ubsan-rt", compile.bundle_ubsan_rt); | |
| 1532 | try addFlag(&zig_args, "dll-export-fns", compile.dll_export_fns); | |
| 1533 | if (compile.rdynamic) { | |
| 1534 | try zig_args.append("-rdynamic"); | |
| 1535 | } | |
| 1536 | if (compile.import_memory) { | |
| 1537 | try zig_args.append("--import-memory"); | |
| 1538 | } | |
| 1539 | if (compile.export_memory) { | |
| 1540 | try zig_args.append("--export-memory"); | |
| 1541 | } | |
| 1542 | if (compile.import_symbols) { | |
| 1543 | try zig_args.append("--import-symbols"); | |
| 1544 | } | |
| 1545 | if (compile.import_table) { | |
| 1546 | try zig_args.append("--import-table"); | |
| 1547 | } | |
| 1548 | if (compile.export_table) { | |
| 1549 | try zig_args.append("--export-table"); | |
| 1550 | } | |
| 1551 | if (compile.initial_memory) |initial_memory| { | |
| 1552 | try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory})); | |
| 1553 | } | |
| 1554 | if (compile.max_memory) |max_memory| { | |
| 1555 | try zig_args.append(b.fmt("--max-memory={d}", .{max_memory})); | |
| 1556 | } | |
| 1557 | if (compile.shared_memory) { | |
| 1558 | try zig_args.append("--shared-memory"); | |
| 1559 | } | |
| 1560 | if (compile.global_base) |global_base| { | |
| 1561 | try zig_args.append(b.fmt("--global-base={d}", .{global_base})); | |
| 1562 | } | |
| 1563 | ||
| 1564 | if (compile.wasi_exec_model) |model| { | |
| 1565 | try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)})); | |
| 1566 | } | |
| 1567 | if (compile.linker_script) |linker_script| { | |
| 1568 | try zig_args.append("--script"); | |
| 1569 | try zig_args.append(linker_script.getPath2(b, step)); | |
| 1570 | } | |
| 1571 | ||
| 1572 | if (compile.version_script) |version_script| { | |
| 1573 | try zig_args.append("--version-script"); | |
| 1574 | try zig_args.append(version_script.getPath2(b, step)); | |
| 1575 | } | |
| 1576 | if (compile.linker_allow_undefined_version) |x| { | |
| 1577 | try zig_args.append(if (x) "--undefined-version" else "--no-undefined-version"); | |
| 1578 | } | |
| 1579 | ||
| 1580 | if (compile.linker_enable_new_dtags) |enabled| { | |
| 1581 | try zig_args.append(if (enabled) "--enable-new-dtags" else "--disable-new-dtags"); | |
| 1582 | } | |
| 1583 | ||
| 1584 | if (compile.kind == .@"test") { | |
| 1585 | if (compile.exec_cmd_args) |exec_cmd_args| { | |
| 1586 | for (exec_cmd_args) |cmd_arg| { | |
| 1587 | if (cmd_arg) |arg| { | |
| 1588 | try zig_args.append("--test-cmd"); | |
| 1589 | try zig_args.append(arg); | |
| 1590 | } else { | |
| 1591 | try zig_args.append("--test-cmd-bin"); | |
| 1592 | } | |
| 1593 | } | |
| 1594 | } | |
| 1595 | } | |
| 1596 | ||
| 1597 | if (b.sysroot) |sysroot| { | |
| 1598 | try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); | |
| 1599 | } | |
| 1600 | ||
| 1601 | // -I and -L arguments that appear after the last --mod argument apply to all modules. | |
| 1602 | const cwd: Io.Dir = .cwd(); | |
| 1603 | const io = b.graph.io; | |
| 1604 | ||
| 1605 | for (b.search_prefixes.items) |search_prefix| { | |
| 1606 | var prefix_dir = cwd.openDir(io, search_prefix, .{}) catch |err| { | |
| 1607 | return step.fail("unable to open prefix directory '{s}': {s}", .{ | |
| 1608 | search_prefix, @errorName(err), | |
| 1609 | }); | |
| 1610 | }; | |
| 1611 | defer prefix_dir.close(io); | |
| 1612 | ||
| 1613 | // Avoid passing -L and -I flags for nonexistent directories. | |
| 1614 | // This prevents a warning, that should probably be upgraded to an error in Zig's | |
| 1615 | // CLI parsing code, when the linker sees an -L directory that does not exist. | |
| 1616 | ||
| 1617 | if (prefix_dir.access(io, "lib", .{})) |_| { | |
| 1618 | try zig_args.appendSlice(&.{ | |
| 1619 | "-L", b.pathJoin(&.{ search_prefix, "lib" }), | |
| 1620 | }); | |
| 1621 | } else |err| switch (err) { | |
| 1622 | error.FileNotFound => {}, | |
| 1623 | else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ | |
| 1624 | search_prefix, @errorName(e), | |
| 1625 | }), | |
| 1626 | } | |
| 1627 | ||
| 1628 | if (prefix_dir.access(io, "include", .{})) |_| { | |
| 1629 | try zig_args.appendSlice(&.{ | |
| 1630 | "-I", b.pathJoin(&.{ search_prefix, "include" }), | |
| 1631 | }); | |
| 1632 | } else |err| switch (err) { | |
| 1633 | error.FileNotFound => {}, | |
| 1634 | else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ | |
| 1635 | search_prefix, @errorName(e), | |
| 1636 | }), | |
| 1637 | } | |
| 1638 | } | |
| 1639 | ||
| 1640 | if (compile.rc_includes != .any) { | |
| 1641 | try zig_args.append("-rcincludes"); | |
| 1642 | try zig_args.append(@tagName(compile.rc_includes)); | |
| 1643 | } | |
| 1644 | ||
| 1645 | try addFlag(&zig_args, "each-lib-rpath", compile.each_lib_rpath); | |
| 1646 | ||
| 1647 | if (compile.build_id orelse b.build_id) |build_id| { | |
| 1648 | try zig_args.append(switch (build_id) { | |
| 1649 | .hexstring => |hs| b.fmt("--build-id=0x{x}", .{hs.toSlice()}), | |
| 1650 | .none, .fast, .uuid, .sha1, .md5 => b.fmt("--build-id={s}", .{@tagName(build_id)}), | |
| 1651 | }); | |
| 1652 | } | |
| 1653 | ||
| 1654 | const opt_zig_lib_dir = if (compile.zig_lib_dir) |dir| | |
| 1655 | dir.getPath2(b, step) | |
| 1656 | else if (b.graph.zig_lib_directory.path) |_| | |
| 1657 | b.fmt("{f}", .{b.graph.zig_lib_directory}) | |
| 1658 | else | |
| 1659 | null; | |
| 1660 | ||
| 1661 | if (opt_zig_lib_dir) |zig_lib_dir| { | |
| 1662 | try zig_args.append("--zig-lib-dir"); | |
| 1663 | try zig_args.append(zig_lib_dir); | |
| 1664 | } | |
| 1665 | ||
| 1666 | try addFlag(&zig_args, "PIE", compile.pie); | |
| 1667 | ||
| 1668 | if (compile.lto) |lto| { | |
| 1669 | try zig_args.append(switch (lto) { | |
| 1670 | .full => "-flto=full", | |
| 1671 | .thin => "-flto=thin", | |
| 1672 | .none => "-fno-lto", | |
| 1673 | }); | |
| 1674 | } | |
| 1675 | ||
| 1676 | try addFlag(&zig_args, "sanitize-coverage-trace-pc-guard", compile.sanitize_coverage_trace_pc_guard); | |
| 1677 | ||
| 1678 | if (compile.subsystem) |subsystem| { | |
| 1679 | try zig_args.append("--subsystem"); | |
| 1680 | try zig_args.append(@tagName(subsystem)); | |
| 1681 | } | |
| 1682 | ||
| 1683 | if (compile.mingw_unicode_entry_point) { | |
| 1684 | try zig_args.append("-municode"); | |
| 1685 | } | |
| 1686 | ||
| 1687 | if (compile.error_limit) |err_limit| try zig_args.appendSlice(&.{ | |
| 1688 | "--error-limit", b.fmt("{d}", .{err_limit}), | |
| 1689 | }); | |
| 1690 | ||
| 1691 | try addFlag(&zig_args, "incremental", b.graph.incremental); | |
| 1692 | ||
| 1693 | try zig_args.append("--listen=-"); | |
| 1694 | ||
| 1695 | // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux | |
| 1696 | // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and | |
| 1697 | // pass that to zig, e.g. via 'zig build-lib @args.rsp' | |
| 1698 | // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html | |
| 1699 | var args_length: usize = 0; | |
| 1700 | for (zig_args.items) |arg| { | |
| 1701 | args_length += arg.len + 1; // +1 to account for null terminator | |
| 1702 | } | |
| 1703 | if (args_length >= 30 * 1024) { | |
| 1704 | try b.cache_root.handle.createDirPath(io, "args"); | |
| 1705 | ||
| 1706 | const args_to_escape = zig_args.items[2..]; | |
| 1707 | var escaped_args = try std.array_list.Managed([]const u8).initCapacity(arena, args_to_escape.len); | |
| 1708 | arg_blk: for (args_to_escape) |arg| { | |
| 1709 | for (arg, 0..) |c, arg_idx| { | |
| 1710 | if (c == '\\' or c == '"') { | |
| 1711 | // Slow path for arguments that need to be escaped. We'll need to allocate and copy | |
| 1712 | var escaped: std.ArrayList(u8) = .empty; | |
| 1713 | try escaped.ensureTotalCapacityPrecise(arena, arg.len + 1); | |
| 1714 | try escaped.appendSlice(arena, arg[0..arg_idx]); | |
| 1715 | for (arg[arg_idx..]) |to_escape| { | |
| 1716 | if (to_escape == '\\' or to_escape == '"') try escaped.append(arena, '\\'); | |
| 1717 | try escaped.append(arena, to_escape); | |
| 1718 | } | |
| 1719 | escaped_args.appendAssumeCapacity(escaped.items); | |
| 1720 | continue :arg_blk; | |
| 1721 | } | |
| 1722 | } | |
| 1723 | escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument | |
| 1724 | } | |
| 1725 | ||
| 1726 | // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with | |
| 1727 | // other zig build commands running in parallel. | |
| 1728 | const partially_quoted = try std.mem.join(arena, "\" \"", escaped_args.items); | |
| 1729 | const args = try std.mem.concat(arena, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); | |
| 1730 | ||
| 1731 | var args_hash: [Sha256.digest_length]u8 = undefined; | |
| 1732 | Sha256.hash(args, &args_hash, .{}); | |
| 1733 | var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; | |
| 1734 | _ = try std.fmt.bufPrint(&args_hex_hash, "{x}", .{&args_hash}); | |
| 1735 | ||
| 1736 | const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; | |
| 1737 | if (b.cache_root.handle.access(io, args_file, .{})) |_| { | |
| 1738 | // The args file is already present from a previous run. | |
| 1739 | } else |err| switch (err) { | |
| 1740 | error.FileNotFound => { | |
| 1741 | var af = b.cache_root.handle.createFileAtomic(io, args_file, .{ | |
| 1742 | .replace = false, | |
| 1743 | .make_path = true, | |
| 1744 | }) catch |e| return step.fail("failed creating tmp args file {f}{s}: {t}", .{ | |
| 1745 | b.cache_root, args_file, e, | |
| 1746 | }); | |
| 1747 | defer af.deinit(io); | |
| 1748 | ||
| 1749 | af.file.writeStreamingAll(io, args) catch |e| { | |
| 1750 | return step.fail("failed writing args data to tmp file {f}{s}: {t}", .{ | |
| 1751 | b.cache_root, args_file, e, | |
| 1752 | }); | |
| 1753 | }; | |
| 1754 | // Note we can't clean up this file, not even after build | |
| 1755 | // success, because that might interfere with another build | |
| 1756 | // process that needs the same file. | |
| 1757 | af.link(io) catch |e| switch (e) { | |
| 1758 | error.PathAlreadyExists => { | |
| 1759 | // The args file was created by another concurrent build process. | |
| 1760 | }, | |
| 1761 | else => |other_err| return step.fail("failed linking tmp file {f}{s}: {t}", .{ | |
| 1762 | b.cache_root, args_file, other_err, | |
| 1763 | }), | |
| 1764 | }; | |
| 1765 | }, | |
| 1766 | else => |other_err| return other_err, | |
| 1767 | } | |
| 1768 | ||
| 1769 | const resolved_args_file = try mem.concat(arena, u8, &.{ | |
| 1770 | "@", | |
| 1771 | try b.cache_root.join(arena, &.{args_file}), | |
| 1772 | }); | |
| 1773 | ||
| 1774 | zig_args.shrinkRetainingCapacity(2); | |
| 1775 | try zig_args.append(resolved_args_file); | |
| 1776 | } | |
| 1777 | ||
| 1778 | return try zig_args.toOwnedSlice(); | |
| 1779 | } | |
| 1780 | ||
| 1781 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 1782 | const b = step.owner; | |
| 1783 | const compile: *Compile = @fieldParentPtr("step", step); | |
| 1784 | ||
| 1785 | const zig_args = try getZigArgs(compile, false); | |
| 1786 | ||
| 1787 | const maybe_output_dir = step.evalZigProcess( | |
| 1788 | zig_args, | |
| 1789 | options.progress_node, | |
| 1790 | (b.graph.incremental == true) and (options.watch or options.web_server != null), | |
| 1791 | options.web_server, | |
| 1792 | options.gpa, | |
| 1793 | ) catch |err| switch (err) { | |
| 1794 | error.NeedCompileErrorCheck => { | |
| 1795 | assert(compile.expect_errors != null); | |
| 1796 | try checkCompileErrors(compile); | |
| 1797 | return; | |
| 1798 | }, | |
| 1799 | else => |e| return e, | |
| 1800 | }; | |
| 1801 | ||
| 1802 | // Update generated files | |
| 1803 | if (maybe_output_dir) |output_dir| { | |
| 1804 | if (compile.emit_directory) |lp| { | |
| 1805 | lp.path = b.fmt("{f}", .{output_dir}); | |
| 1806 | } | |
| 1807 | ||
| 1808 | // zig fmt: off | |
| 1809 | if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin); | |
| 1810 | if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb); | |
| 1811 | // hack for stage2_x86_64 + coff | |
| 1812 | if (compile.generated_compiler_rt_dyn_lib) |lp| lp.path = compile.outputPath(output_dir, .compiler_rt_dyn_lib); | |
| 1813 | if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib); | |
| 1814 | if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h); | |
| 1815 | if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs); | |
| 1816 | if (compile.generated_asm) |lp| lp.path = compile.outputPath(output_dir, .@"asm"); | |
| 1817 | if (compile.generated_llvm_ir) |lp| lp.path = compile.outputPath(output_dir, .llvm_ir); | |
| 1818 | if (compile.generated_llvm_bc) |lp| lp.path = compile.outputPath(output_dir, .llvm_bc); | |
| 1819 | // zig fmt: on | |
| 1820 | } | |
| 1821 | ||
| 1822 | if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic and | |
| 1823 | compile.version != null and compile.generated_bin != null and | |
| 1824 | std.Build.wantSharedLibSymLinks(compile.rootModuleTarget())) | |
| 1825 | { | |
| 1826 | try doAtomicSymLinks( | |
| 1827 | step, | |
| 1828 | compile.getEmittedBin().getPath2(b, step), | |
| 1829 | compile.major_only_filename.?, | |
| 1830 | compile.name_only_filename.?, | |
| 1831 | ); | |
| 1832 | } | |
| 1833 | } | |
| 1834 | fn outputPath(c: *Compile, out_dir: std.Build.Cache.Path, ea: std.zig.EmitArtifact) []const u8 { | |
| 1835 | const arena = c.step.owner.graph.arena; | |
| 1836 | const name = ea.cacheName(arena, .{ | |
| 1837 | .root_name = c.name, | |
| 1838 | .target = &c.root_module.resolved_target.?.result, | |
| 1839 | .output_mode = switch (c.kind) { | |
| 1840 | .lib => .Lib, | |
| 1841 | .obj, .test_obj => .Obj, | |
| 1842 | .exe, .@"test" => .Exe, | |
| 1843 | }, | |
| 1844 | .link_mode = c.linkage, | |
| 1845 | .version = c.version, | |
| 1846 | }) catch @panic("OOM"); | |
| 1847 | return out_dir.joinString(arena, name) catch @panic("OOM"); | |
| 1848 | } | |
| 1849 | ||
| 1850 | pub fn rebuildInFuzzMode(c: *Compile, gpa: Allocator, progress_node: std.Progress.Node) !Path { | |
| 1851 | c.step.result_error_msgs.clearRetainingCapacity(); | |
| 1852 | c.step.result_stderr = ""; | |
| 1853 | ||
| 1854 | c.step.result_error_bundle.deinit(gpa); | |
| 1855 | c.step.result_error_bundle = std.zig.ErrorBundle.empty; | |
| 1856 | ||
| 1857 | if (c.step.result_failed_command) |cmd| { | |
| 1858 | gpa.free(cmd); | |
| 1859 | c.step.result_failed_command = null; | |
| 1860 | } | |
| 1861 | ||
| 1862 | const zig_args = try getZigArgs(c, true); | |
| 1863 | const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false, null, gpa); | |
| 1864 | return maybe_output_bin_path.?; | |
| 1865 | } | |
| 1866 | ||
| 1867 | pub fn doAtomicSymLinks( | |
| 1868 | step: *Step, | |
| 1869 | output_path: []const u8, | |
| 1870 | filename_major_only: []const u8, | |
| 1871 | filename_name_only: []const u8, | |
| 1872 | ) !void { | |
| 1873 | const b = step.owner; | |
| 1874 | const io = b.graph.io; | |
| 1875 | const out_dir = fs.path.dirname(output_path) orelse "."; | |
| 1876 | const out_basename = fs.path.basename(output_path); | |
| 1877 | // sym link for libfoo.so.1 to libfoo.so.1.2.3 | |
| 1878 | const major_only_path = b.pathJoin(&.{ out_dir, filename_major_only }); | |
| 1879 | const cwd: Io.Dir = .cwd(); | |
| 1880 | cwd.symLinkAtomic(io, out_basename, major_only_path, .{}) catch |err| { | |
| 1881 | return step.fail("unable to symlink {s} -> {s}: {s}", .{ | |
| 1882 | major_only_path, out_basename, @errorName(err), | |
| 1883 | }); | |
| 1884 | }; | |
| 1885 | // sym link for libfoo.so to libfoo.so.1 | |
| 1886 | const name_only_path = b.pathJoin(&.{ out_dir, filename_name_only }); | |
| 1887 | cwd.symLinkAtomic(io, filename_major_only, name_only_path, .{}) catch |err| { | |
| 1888 | return step.fail("Unable to symlink {s} -> {s}: {s}", .{ | |
| 1889 | name_only_path, filename_major_only, @errorName(err), | |
| 1890 | }); | |
| 1891 | }; | |
| 1892 | } | |
| 1893 | ||
| 1894 | fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg { | |
| 1895 | const pkg_config_exe = b.graph.environ_map.get("PKG_CONFIG") orelse "pkg-config"; | |
| 1896 | const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .ignore); | |
| 1897 | var list = std.array_list.Managed(PkgConfigPkg).init(b.allocator); | |
| 1898 | errdefer list.deinit(); | |
| 1899 | var line_it = mem.tokenizeAny(u8, stdout, "\r\n"); | |
| 1900 | while (line_it.next()) |line| { | |
| 1901 | if (mem.trim(u8, line, " \t").len == 0) continue; | |
| 1902 | var tok_it = mem.tokenizeAny(u8, line, " \t"); | |
| 1903 | try list.append(PkgConfigPkg{ | |
| 1904 | .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, | |
| 1905 | .desc = tok_it.rest(), | |
| 1906 | }); | |
| 1907 | } | |
| 1908 | return list.toOwnedSlice(); | |
| 1909 | } | |
| 1910 | ||
| 1911 | fn getPkgConfigList(b: *std.Build) ![]const PkgConfigPkg { | |
| 1912 | if (b.pkg_config_pkg_list) |res| { | |
| 1913 | return res; | |
| 1914 | } | |
| 1915 | var code: u8 = undefined; | |
| 1916 | if (execPkgConfigList(b, &code)) |list| { | |
| 1917 | b.pkg_config_pkg_list = list; | |
| 1918 | return list; | |
| 1919 | } else |err| { | |
| 1920 | const result = switch (err) { | |
| 1921 | error.ProcessTerminated => error.PkgConfigCrashed, | |
| 1922 | error.ExecNotSupported => error.PkgConfigFailed, | |
| 1923 | error.ExitCodeFailure => error.PkgConfigFailed, | |
| 1924 | error.FileNotFound => error.PkgConfigNotInstalled, | |
| 1925 | error.InvalidName => error.PkgConfigNotInstalled, | |
| 1926 | error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, | |
| 1927 | else => return err, | |
| 1928 | }; | |
| 1929 | b.pkg_config_pkg_list = result; | |
| 1930 | return result; | |
| 1931 | } | |
| 1932 | } | |
| 1933 | ||
| 1934 | fn addFlag(args: *std.array_list.Managed([]const u8), comptime name: []const u8, opt: ?bool) !void { | |
| 1935 | const cond = opt orelse return; | |
| 1936 | try args.ensureUnusedCapacity(1); | |
| 1937 | if (cond) { | |
| 1938 | args.appendAssumeCapacity("-f" ++ name); | |
| 1939 | } else { | |
| 1940 | args.appendAssumeCapacity("-fno-" ++ name); | |
| 1941 | } | |
| 1942 | } | |
| 1943 | ||
| 1944 | fn checkCompileErrors(compile: *Compile) !void { | |
| 1945 | // Clear this field so that it does not get printed by the build runner. | |
| 1946 | const actual_eb = compile.step.result_error_bundle; | |
| 1947 | compile.step.result_error_bundle = .empty; | |
| 1948 | ||
| 1949 | const arena = compile.step.owner.allocator; | |
| 1950 | ||
| 1951 | const actual_errors = ae: { | |
| 1952 | var aw: std.Io.Writer.Allocating = .init(arena); | |
| 1953 | defer aw.deinit(); | |
| 1954 | try actual_eb.renderToWriter(.{ | |
| 1955 | .include_reference_trace = false, | |
| 1956 | .include_source_line = false, | |
| 1957 | }, &aw.writer); | |
| 1958 | break :ae try aw.toOwnedSlice(); | |
| 1959 | }; | |
| 1960 | ||
| 1961 | // Render the expected lines into a string that we can compare verbatim. | |
| 1962 | var expected_generated: std.ArrayList(u8) = .empty; | |
| 1963 | const expect_errors = compile.expect_errors.?; | |
| 1964 | ||
| 1965 | var actual_line_it = mem.splitScalar(u8, actual_errors, '\n'); | |
| 1966 | ||
| 1967 | // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile | |
| 1968 | switch (expect_errors) { | |
| 1969 | .starts_with => |expect_starts_with| { | |
| 1970 | if (std.mem.startsWith(u8, actual_errors, expect_starts_with)) return; | |
| 1971 | return compile.step.fail( | |
| 1972 | \\ | |
| 1973 | \\========= should start with: ============ | |
| 1974 | \\{s} | |
| 1975 | \\========= but not found: ================ | |
| 1976 | \\{s} | |
| 1977 | \\========================================= | |
| 1978 | , .{ expect_starts_with, actual_errors }); | |
| 1979 | }, | |
| 1980 | .contains => |expect_line| { | |
| 1981 | while (actual_line_it.next()) |actual_line| { | |
| 1982 | if (!matchCompileError(actual_line, expect_line)) continue; | |
| 1983 | return; | |
| 1984 | } | |
| 1985 | ||
| 1986 | return compile.step.fail( | |
| 1987 | \\ | |
| 1988 | \\========= should contain: =============== | |
| 1989 | \\{s} | |
| 1990 | \\========= but not found: ================ | |
| 1991 | \\{s} | |
| 1992 | \\========================================= | |
| 1993 | , .{ expect_line, actual_errors }); | |
| 1994 | }, | |
| 1995 | .stderr_contains => |expect_line| { | |
| 1996 | const actual_stderr: []const u8 = if (compile.step.result_error_msgs.items.len > 0) | |
| 1997 | compile.step.result_error_msgs.items[0] | |
| 1998 | else | |
| 1999 | &.{}; | |
| 2000 | compile.step.result_error_msgs.clearRetainingCapacity(); | |
| 2001 | ||
| 2002 | var stderr_line_it = mem.splitScalar(u8, actual_stderr, '\n'); | |
| 2003 | ||
| 2004 | while (stderr_line_it.next()) |actual_line| { | |
| 2005 | if (!matchCompileError(actual_line, expect_line)) continue; | |
| 2006 | return; | |
| 2007 | } | |
| 2008 | ||
| 2009 | return compile.step.fail( | |
| 2010 | \\ | |
| 2011 | \\========= should contain: =============== | |
| 2012 | \\{s} | |
| 2013 | \\========= but not found: ================ | |
| 2014 | \\{s} | |
| 2015 | \\========================================= | |
| 2016 | , .{ expect_line, actual_stderr }); | |
| 2017 | }, | |
| 2018 | .exact => |expect_lines| { | |
| 2019 | for (expect_lines) |expect_line| { | |
| 2020 | const actual_line = actual_line_it.next() orelse { | |
| 2021 | try expected_generated.appendSlice(arena, expect_line); | |
| 2022 | try expected_generated.append(arena, '\n'); | |
| 2023 | continue; | |
| 2024 | }; | |
| 2025 | if (matchCompileError(actual_line, expect_line)) { | |
| 2026 | try expected_generated.appendSlice(arena, actual_line); | |
| 2027 | try expected_generated.append(arena, '\n'); | |
| 2028 | continue; | |
| 2029 | } | |
| 2030 | try expected_generated.appendSlice(arena, expect_line); | |
| 2031 | try expected_generated.append(arena, '\n'); | |
| 2032 | } | |
| 2033 | ||
| 2034 | if (mem.eql(u8, expected_generated.items, actual_errors)) return; | |
| 2035 | ||
| 2036 | return compile.step.fail( | |
| 2037 | \\ | |
| 2038 | \\========= expected: ===================== | |
| 2039 | \\{s} | |
| 2040 | \\========= but found: ==================== | |
| 2041 | \\{s} | |
| 2042 | \\========================================= | |
| 2043 | , .{ expected_generated.items, actual_errors }); | |
| 2044 | }, | |
| 2045 | } | |
| 2046 | } | |
| 2047 | ||
| 2048 | fn matchCompileError(actual: []const u8, expected: []const u8) bool { | |
| 2049 | if (mem.endsWith(u8, actual, expected)) return true; | |
| 2050 | if (mem.startsWith(u8, expected, ":?:?: ")) { | |
| 2051 | if (mem.endsWith(u8, actual, expected[":?:?: ".len..])) return true; | |
| 2052 | } | |
| 2053 | // We scan for /?/ in expected line and if there is a match, we match everything | |
| 2054 | // up to and after /?/. | |
| 2055 | const expected_trim = mem.trim(u8, expected, " "); | |
| 2056 | if (mem.find(u8, expected_trim, "/?/")) |index| { | |
| 2057 | const actual_trim = mem.trim(u8, actual, " "); | |
| 2058 | const lhs = expected_trim[0..index]; | |
| 2059 | const rhs = expected_trim[index + "/?/".len ..]; | |
| 2060 | if (mem.startsWith(u8, actual_trim, lhs) and mem.endsWith(u8, actual_trim, rhs)) return true; | |
| 2061 | } | |
| 2062 | return false; | |
| 2063 | } | |
| 2064 | ||
| 2065 | 750 | pub fn rootModuleTarget(c: *Compile) std.Target { |
| 2066 | 751 | // The root module is always given a target, so we know this to be non-null. |
| 2067 | 752 | return c.root_module.resolved_target.?.result; |
| 2068 | 753 | } |
| 2069 | 754 | |
| 2070 | fn moduleNeedsCliArg(mod: *const Module) bool { | |
| 2071 | return for (mod.link_objects.items) |o| switch (o) { | |
| 2072 | .c_source_file, .c_source_files, .assembly_file, .win32_resource_file => break true, | |
| 2073 | else => continue, | |
| 2074 | } else false; | |
| 2075 | } | |
| 2076 | ||
| 2077 | 755 | /// Return the full set of `Step.Compile` which `start` depends on, recursively. `start` itself is |
| 2078 | 756 | /// always returned as the first element. If `chase_dynamic` is `false`, then dynamic libraries are |
| 2079 | 757 | /// not included, and their dependencies are not considered; if `chase_dynamic` is `true`, dynamic |
lib/std/Build/Step/ConfigHeader.zig+55-944| ... | ... | @@ -4,7 +4,20 @@ const std = @import("std"); |
| 4 | 4 | const Io = std.Io; |
| 5 | 5 | const Step = std.Build.Step; |
| 6 | 6 | const Allocator = std.mem.Allocator; |
| 7 | const Writer = std.Io.Writer; | |
| 7 | const Configuration = std.Build.Configuration; | |
| 8 | const allocPrint = std.fmt.allocPrint; | |
| 9 | ||
| 10 | step: Step, | |
| 11 | values: std.array_hash_map.String(Value) = .empty, | |
| 12 | /// This directory contains the generated file under the name `include_path`. | |
| 13 | generated_dir: Configuration.GeneratedFileIndex, | |
| 14 | ||
| 15 | style: Style, | |
| 16 | input_size_limit: ?u64, | |
| 17 | include_path: []const u8, | |
| 18 | include_guard: Configuration.OptionalString, | |
| 19 | ||
| 20 | pub const base_tag: Step.Tag = .config_header; | |
| 8 | 21 | |
| 9 | 22 | pub const Style = union(enum) { |
| 10 | 23 | /// A configure format supported by autotools that uses `#undef foo` to |
| ... | ... | @@ -37,70 +50,60 @@ pub const Value = union(enum) { |
| 37 | 50 | string: []const u8, |
| 38 | 51 | }; |
| 39 | 52 | |
| 40 | step: Step, | |
| 41 | values: std.array_hash_map.String(Value), | |
| 42 | /// This directory contains the generated file under the name `include_path`. | |
| 43 | generated_dir: std.Build.GeneratedFile, | |
| 44 | ||
| 45 | style: Style, | |
| 46 | max_bytes: usize, | |
| 47 | include_path: []const u8, | |
| 48 | include_guard_override: ?[]const u8, | |
| 49 | ||
| 50 | pub const base_id: Step.Id = .config_header; | |
| 51 | ||
| 52 | 53 | pub const Options = struct { |
| 53 | 54 | style: Style = .blank, |
| 54 | max_bytes: usize = 2 * 1024 * 1024, | |
| 55 | max_bytes: ?u64 = null, | |
| 55 | 56 | include_path: ?[]const u8 = null, |
| 57 | include_guard: ?[]const u8 = null, | |
| 56 | 58 | first_ret_addr: ?usize = null, |
| 57 | include_guard_override: ?[]const u8 = null, | |
| 58 | 59 | }; |
| 59 | 60 | |
| 60 | 61 | pub fn create(owner: *std.Build, options: Options) *ConfigHeader { |
| 61 | const config_header = owner.allocator.create(ConfigHeader) catch @panic("OOM"); | |
| 62 | ||
| 63 | var include_path: []const u8 = "config.h"; | |
| 64 | ||
| 65 | if (options.style.getPath()) |s| default_include_path: { | |
| 66 | const sub_path = switch (s) { | |
| 67 | .src_path => |sp| sp.sub_path, | |
| 68 | .generated => break :default_include_path, | |
| 69 | .cwd_relative => |sub_path| sub_path, | |
| 70 | .dependency => |dependency| dependency.sub_path, | |
| 71 | }; | |
| 72 | const basename = std.fs.path.basename(sub_path); | |
| 73 | if (std.mem.endsWith(u8, basename, ".h.in")) { | |
| 74 | include_path = basename[0 .. basename.len - 3]; | |
| 62 | const graph = owner.graph; | |
| 63 | const arena = graph.arena; | |
| 64 | const wc = &graph.wip_configuration; | |
| 65 | const config_header = graph.create(ConfigHeader); | |
| 66 | ||
| 67 | const include_path: []const u8 = p: { | |
| 68 | if (options.include_path) |p| | |
| 69 | break :p graph.dupeString(p); | |
| 70 | ||
| 71 | if (options.style.getPath()) |s| default: { | |
| 72 | const sub_path = switch (s) { | |
| 73 | .src_path => |sp| sp.sub_path, | |
| 74 | .generated => break :default, | |
| 75 | .cwd_relative => |sub_path| sub_path, | |
| 76 | .relative => |r| r.sub_path, | |
| 77 | .dependency => |dependency| dependency.sub_path, | |
| 78 | }; | |
| 79 | const basename = Io.Dir.path.basename(sub_path); | |
| 80 | if (std.mem.endsWith(u8, basename, ".h.in")) | |
| 81 | break :p graph.dupeString(basename[0 .. basename.len - 3]); | |
| 75 | 82 | } |
| 76 | } | |
| 77 | ||
| 78 | if (options.include_path) |p| { | |
| 79 | include_path = p; | |
| 80 | } | |
| 83 | break :p "config.h"; | |
| 84 | }; | |
| 81 | 85 | |
| 82 | 86 | const name = if (options.style.getPath()) |s| |
| 83 | owner.fmt("configure {s} header {s} to {s}", .{ | |
| 84 | @tagName(options.style), s.getDisplayName(), include_path, | |
| 85 | }) | |
| 87 | allocPrint(arena, "configure {t} header {f} to {s}", .{ | |
| 88 | options.style, s, include_path, | |
| 89 | }) catch @panic("OOM") | |
| 86 | 90 | else |
| 87 | owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path }); | |
| 91 | allocPrint(arena, "configure {t} header to {s}", .{ | |
| 92 | options.style, include_path, | |
| 93 | }) catch @panic("OOM"); | |
| 88 | 94 | |
| 89 | 95 | config_header.* = .{ |
| 90 | 96 | .step = .init(.{ |
| 91 | .id = base_id, | |
| 97 | .tag = base_tag, | |
| 92 | 98 | .name = name, |
| 93 | 99 | .owner = owner, |
| 94 | .makeFn = make, | |
| 95 | 100 | .first_ret_addr = options.first_ret_addr orelse @returnAddress(), |
| 96 | 101 | }), |
| 97 | 102 | .style = options.style, |
| 98 | .values = .empty, | |
| 99 | ||
| 100 | .max_bytes = options.max_bytes, | |
| 103 | .input_size_limit = options.max_bytes, | |
| 101 | 104 | .include_path = include_path, |
| 102 | .include_guard_override = options.include_guard_override, | |
| 103 | .generated_dir = .{ .step = &config_header.step }, | |
| 105 | .include_guard = if (options.include_guard) |s| .init(wc.addString(s) catch @panic("OOM")) else .none, | |
| 106 | .generated_dir = graph.addGeneratedFile(&config_header.step), | |
| 104 | 107 | }; |
| 105 | 108 | |
| 106 | 109 | if (options.style.getPath()) |s| { |
| ... | ... | @@ -118,19 +121,6 @@ pub fn addValue(config_header: *ConfigHeader, name: []const u8, comptime T: type |
| 118 | 121 | return addValueInner(config_header, name, T, value) catch @panic("OOM"); |
| 119 | 122 | } |
| 120 | 123 | |
| 121 | pub fn addValues(config_header: *ConfigHeader, values: anytype) void { | |
| 122 | inline for (@typeInfo(@TypeOf(values)).@"struct".fields) |field| { | |
| 123 | addValue(config_header, field.name, field.type, @field(values, field.name)); | |
| 124 | } | |
| 125 | } | |
| 126 | ||
| 127 | pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath { | |
| 128 | return .{ .generated = .{ .file = &ch.generated_dir } }; | |
| 129 | } | |
| 130 | pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath { | |
| 131 | return ch.getOutputDir().path(ch.step.owner, ch.include_path); | |
| 132 | } | |
| 133 | ||
| 134 | 124 | fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: type, value: T) !void { |
| 135 | 125 | const arena = config_header.step.owner.allocator; |
| 136 | 126 | switch (@typeInfo(T)) { |
| ... | ... | @@ -182,895 +172,16 @@ fn addValueInner(config_header: *ConfigHeader, name: []const u8, comptime T: typ |
| 182 | 172 | } |
| 183 | 173 | } |
| 184 | 174 | |
| 185 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 186 | _ = options; | |
| 187 | const b = step.owner; | |
| 188 | const config_header: *ConfigHeader = @fieldParentPtr("step", step); | |
| 189 | if (config_header.style.getPath()) |lp| try step.singleUnchangingWatchInput(lp); | |
| 190 | ||
| 191 | const gpa = b.allocator; | |
| 192 | const arena = b.allocator; | |
| 193 | const io = b.graph.io; | |
| 194 | ||
| 195 | var man = b.graph.cache.obtain(); | |
| 196 | defer man.deinit(); | |
| 197 | ||
| 198 | // Random bytes to make ConfigHeader unique. Refresh this with new | |
| 199 | // random bytes when ConfigHeader implementation is modified in a | |
| 200 | // non-backwards-compatible way. | |
| 201 | man.hash.add(@as(u32, 0xdef08d23)); | |
| 202 | man.hash.addBytes(config_header.include_path); | |
| 203 | man.hash.addOptionalBytes(config_header.include_guard_override); | |
| 204 | ||
| 205 | var aw: Writer.Allocating = .init(gpa); | |
| 206 | defer aw.deinit(); | |
| 207 | const bw = &aw.writer; | |
| 208 | ||
| 209 | const header_text = "This file was generated by ConfigHeader using the Zig Build System."; | |
| 210 | const c_generated_line = "/* " ++ header_text ++ " */\n"; | |
| 211 | const asm_generated_line = "; " ++ header_text ++ "\n"; | |
| 212 | ||
| 213 | switch (config_header.style) { | |
| 214 | .autoconf_undef, .autoconf_at => |file_source| { | |
| 215 | try bw.writeAll(c_generated_line); | |
| 216 | const src_path = file_source.getPath2(b, step); | |
| 217 | const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| { | |
| 218 | return step.fail("unable to read autoconf input file '{s}': {s}", .{ | |
| 219 | src_path, @errorName(err), | |
| 220 | }); | |
| 221 | }; | |
| 222 | switch (config_header.style) { | |
| 223 | .autoconf_undef => try render_autoconf_undef(step, contents, bw, &config_header.values, src_path), | |
| 224 | .autoconf_at => try render_autoconf_at(step, contents, &aw, &config_header.values, src_path), | |
| 225 | else => unreachable, | |
| 226 | } | |
| 227 | }, | |
| 228 | .cmake => |file_source| { | |
| 229 | try bw.writeAll(c_generated_line); | |
| 230 | const src_path = file_source.getPath2(b, step); | |
| 231 | const contents = Io.Dir.cwd().readFileAlloc(io, src_path, arena, .limited(config_header.max_bytes)) catch |err| { | |
| 232 | return step.fail("unable to read cmake input file '{s}': {s}", .{ | |
| 233 | src_path, @errorName(err), | |
| 234 | }); | |
| 235 | }; | |
| 236 | try render_cmake(step, contents, bw, config_header.values, src_path); | |
| 237 | }, | |
| 238 | .blank => { | |
| 239 | try bw.writeAll(c_generated_line); | |
| 240 | try render_blank(gpa, bw, config_header.values, config_header.include_path, config_header.include_guard_override); | |
| 241 | }, | |
| 242 | .nasm => { | |
| 243 | try bw.writeAll(asm_generated_line); | |
| 244 | try render_nasm(bw, config_header.values); | |
| 245 | }, | |
| 246 | } | |
| 247 | ||
| 248 | const output = aw.written(); | |
| 249 | man.hash.addBytes(output); | |
| 250 | ||
| 251 | if (try step.cacheHit(&man)) { | |
| 252 | const digest = man.final(); | |
| 253 | config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest }); | |
| 254 | return; | |
| 255 | } | |
| 256 | ||
| 257 | const digest = man.final(); | |
| 258 | ||
| 259 | // If output_path has directory parts, deal with them. Example: | |
| 260 | // output_dir is zig-cache/o/HASH | |
| 261 | // output_path is libavutil/avconfig.h | |
| 262 | // We want to open directory zig-cache/o/HASH/libavutil/ | |
| 263 | // but keep output_dir as zig-cache/o/HASH for -I include | |
| 264 | const sub_path = b.pathJoin(&.{ "o", &digest, config_header.include_path }); | |
| 265 | const sub_path_dirname = std.fs.path.dirname(sub_path).?; | |
| 266 | ||
| 267 | b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { | |
| 268 | return step.fail("unable to make path '{f}{s}': {s}", .{ | |
| 269 | b.cache_root, sub_path_dirname, @errorName(err), | |
| 270 | }); | |
| 271 | }; | |
| 272 | ||
| 273 | b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = output }) catch |err| { | |
| 274 | return step.fail("unable to write file '{f}{s}': {s}", .{ | |
| 275 | b.cache_root, sub_path, @errorName(err), | |
| 276 | }); | |
| 277 | }; | |
| 278 | ||
| 279 | config_header.generated_dir.path = try b.cache_root.join(arena, &.{ "o", &digest }); | |
| 280 | try man.writeManifest(); | |
| 281 | } | |
| 282 | ||
| 283 | fn render_autoconf_undef( | |
| 284 | step: *Step, | |
| 285 | contents: []const u8, | |
| 286 | bw: *Writer, | |
| 287 | values: *const std.array_hash_map.String(Value), | |
| 288 | src_path: []const u8, | |
| 289 | ) !void { | |
| 290 | const build = step.owner; | |
| 291 | const allocator = build.allocator; | |
| 292 | ||
| 293 | var is_used: std.bit_set.Dynamic = try .initEmpty(allocator, values.count()); | |
| 294 | defer is_used.deinit(allocator); | |
| 295 | ||
| 296 | var any_errors = false; | |
| 297 | var line_index: u32 = 0; | |
| 298 | var line_it = std.mem.splitScalar(u8, contents, '\n'); | |
| 299 | while (line_it.next()) |line| : (line_index += 1) { | |
| 300 | if (!std.mem.startsWith(u8, line, "#")) { | |
| 301 | try bw.writeAll(line); | |
| 302 | try bw.writeByte('\n'); | |
| 303 | continue; | |
| 304 | } | |
| 305 | var it = std.mem.tokenizeAny(u8, line[1..], " \t\r"); | |
| 306 | const undef = it.next().?; | |
| 307 | if (!std.mem.eql(u8, undef, "undef")) { | |
| 308 | try bw.writeAll(line); | |
| 309 | try bw.writeByte('\n'); | |
| 310 | continue; | |
| 311 | } | |
| 312 | const name = it.next().?; | |
| 313 | const index = values.getIndex(name) orelse { | |
| 314 | try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ | |
| 315 | src_path, line_index + 1, name, | |
| 316 | }); | |
| 317 | any_errors = true; | |
| 318 | continue; | |
| 319 | }; | |
| 320 | is_used.set(index); | |
| 321 | try renderValueC(bw, name, values.values()[index]); | |
| 322 | } | |
| 323 | ||
| 324 | var unused_value_it = is_used.iterator(.{ .kind = .unset }); | |
| 325 | while (unused_value_it.next()) |index| { | |
| 326 | try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, values.keys()[index] }); | |
| 327 | any_errors = true; | |
| 328 | } | |
| 329 | ||
| 330 | if (any_errors) { | |
| 331 | return error.MakeFailed; | |
| 332 | } | |
| 333 | } | |
| 334 | ||
| 335 | fn render_autoconf_at( | |
| 336 | step: *Step, | |
| 337 | contents: []const u8, | |
| 338 | aw: *Writer.Allocating, | |
| 339 | values: *const std.array_hash_map.String(Value), | |
| 340 | src_path: []const u8, | |
| 341 | ) !void { | |
| 342 | const build = step.owner; | |
| 343 | const allocator = build.allocator; | |
| 344 | const bw = &aw.writer; | |
| 345 | ||
| 346 | const used = allocator.alloc(bool, values.count()) catch @panic("OOM"); | |
| 347 | for (used) |*u| u.* = false; | |
| 348 | defer allocator.free(used); | |
| 349 | ||
| 350 | var any_errors = false; | |
| 351 | var line_index: u32 = 0; | |
| 352 | var line_it = std.mem.splitScalar(u8, contents, '\n'); | |
| 353 | while (line_it.next()) |line| : (line_index += 1) { | |
| 354 | const last_line = line_it.index == line_it.buffer.len; | |
| 355 | ||
| 356 | const old_len = aw.written().len; | |
| 357 | expand_variables_autoconf_at(bw, line, values, used) catch |err| switch (err) { | |
| 358 | error.MissingValue => { | |
| 359 | const name = aw.written()[old_len..]; | |
| 360 | defer aw.shrinkRetainingCapacity(old_len); | |
| 361 | try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ | |
| 362 | src_path, line_index + 1, name, | |
| 363 | }); | |
| 364 | any_errors = true; | |
| 365 | continue; | |
| 366 | }, | |
| 367 | else => { | |
| 368 | try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{ | |
| 369 | src_path, line_index + 1, @errorName(err), | |
| 370 | }); | |
| 371 | any_errors = true; | |
| 372 | continue; | |
| 373 | }, | |
| 374 | }; | |
| 375 | if (!last_line) try bw.writeByte('\n'); | |
| 376 | } | |
| 377 | ||
| 378 | for (values.entries.slice().items(.key), used) |name, u| { | |
| 379 | if (!u) { | |
| 380 | try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); | |
| 381 | any_errors = true; | |
| 382 | } | |
| 383 | } | |
| 384 | ||
| 385 | if (any_errors) return error.MakeFailed; | |
| 386 | } | |
| 387 | ||
| 388 | fn render_cmake( | |
| 389 | step: *Step, | |
| 390 | contents: []const u8, | |
| 391 | bw: *Writer, | |
| 392 | values: std.array_hash_map.String(Value), | |
| 393 | src_path: []const u8, | |
| 394 | ) !void { | |
| 395 | const build = step.owner; | |
| 396 | const allocator = build.allocator; | |
| 397 | ||
| 398 | var values_copy = try values.clone(allocator); | |
| 399 | defer values_copy.deinit(allocator); | |
| 400 | ||
| 401 | var any_errors = false; | |
| 402 | var line_index: u32 = 0; | |
| 403 | var line_it = std.mem.splitScalar(u8, contents, '\n'); | |
| 404 | while (line_it.next()) |raw_line| : (line_index += 1) { | |
| 405 | const last_line = line_it.index == line_it.buffer.len; | |
| 406 | ||
| 407 | const line = expand_variables_cmake(allocator, raw_line, values) catch |err| switch (err) { | |
| 408 | error.InvalidCharacter => { | |
| 409 | try step.addError("{s}:{d}: error: invalid character in a variable name", .{ | |
| 410 | src_path, line_index + 1, | |
| 411 | }); | |
| 412 | any_errors = true; | |
| 413 | continue; | |
| 414 | }, | |
| 415 | else => { | |
| 416 | try step.addError("{s}:{d}: unable to substitute variable: error: {s}", .{ | |
| 417 | src_path, line_index + 1, @errorName(err), | |
| 418 | }); | |
| 419 | any_errors = true; | |
| 420 | continue; | |
| 421 | }, | |
| 422 | }; | |
| 423 | defer allocator.free(line); | |
| 424 | ||
| 425 | const line_start = std.mem.findNone(u8, line, " \t\r") orelse { | |
| 426 | try bw.writeAll(line); | |
| 427 | if (!last_line) try bw.writeByte('\n'); | |
| 428 | continue; | |
| 429 | }; | |
| 430 | const whitespace_prefix = line[0..line_start]; | |
| 431 | const trimmed_line = line[line_start..]; | |
| 432 | ||
| 433 | if (!std.mem.startsWith(u8, trimmed_line, "#")) { | |
| 434 | try bw.writeAll(line); | |
| 435 | if (!last_line) try bw.writeByte('\n'); | |
| 436 | continue; | |
| 437 | } | |
| 438 | ||
| 439 | var it = std.mem.tokenizeAny(u8, trimmed_line[1..], " \t\r"); | |
| 440 | const cmakedefine = it.next().?; | |
| 441 | if (!std.mem.eql(u8, cmakedefine, "cmakedefine") and | |
| 442 | !std.mem.eql(u8, cmakedefine, "cmakedefine01")) | |
| 443 | { | |
| 444 | try bw.writeAll(line); | |
| 445 | if (!last_line) try bw.writeByte('\n'); | |
| 446 | continue; | |
| 447 | } | |
| 448 | ||
| 449 | const booldefine = std.mem.eql(u8, cmakedefine, "cmakedefine01"); | |
| 450 | ||
| 451 | const name = it.next() orelse { | |
| 452 | try step.addError("{s}:{d}: error: missing define name", .{ | |
| 453 | src_path, line_index + 1, | |
| 454 | }); | |
| 455 | any_errors = true; | |
| 456 | continue; | |
| 457 | }; | |
| 458 | var value = values_copy.get(name) orelse blk: { | |
| 459 | if (booldefine) { | |
| 460 | break :blk Value{ .int = 0 }; | |
| 461 | } | |
| 462 | break :blk Value.undef; | |
| 463 | }; | |
| 464 | ||
| 465 | value = blk: { | |
| 466 | switch (value) { | |
| 467 | .boolean => |b| { | |
| 468 | if (!b) { | |
| 469 | break :blk Value.undef; | |
| 470 | } | |
| 471 | }, | |
| 472 | .int => |i| { | |
| 473 | if (i == 0) { | |
| 474 | break :blk Value.undef; | |
| 475 | } | |
| 476 | }, | |
| 477 | .string => |string| { | |
| 478 | if (string.len == 0) { | |
| 479 | break :blk Value.undef; | |
| 480 | } | |
| 481 | }, | |
| 482 | ||
| 483 | else => {}, | |
| 484 | } | |
| 485 | break :blk value; | |
| 486 | }; | |
| 487 | ||
| 488 | if (booldefine) { | |
| 489 | value = blk: { | |
| 490 | switch (value) { | |
| 491 | .undef => { | |
| 492 | break :blk Value{ .boolean = false }; | |
| 493 | }, | |
| 494 | .defined => { | |
| 495 | break :blk Value{ .boolean = false }; | |
| 496 | }, | |
| 497 | .boolean => |b| { | |
| 498 | break :blk Value{ .boolean = b }; | |
| 499 | }, | |
| 500 | .int => |i| { | |
| 501 | break :blk Value{ .boolean = i != 0 }; | |
| 502 | }, | |
| 503 | .string => |string| { | |
| 504 | break :blk Value{ .boolean = string.len != 0 }; | |
| 505 | }, | |
| 506 | ||
| 507 | else => { | |
| 508 | break :blk Value{ .boolean = false }; | |
| 509 | }, | |
| 510 | } | |
| 511 | }; | |
| 512 | } else if (value != Value.undef) { | |
| 513 | value = Value{ .ident = it.rest() }; | |
| 514 | } | |
| 515 | ||
| 516 | try bw.writeAll(whitespace_prefix); | |
| 517 | try renderValueC(bw, name, value); | |
| 518 | } | |
| 519 | ||
| 520 | if (any_errors) { | |
| 521 | return error.HeaderConfigFailed; | |
| 522 | } | |
| 523 | } | |
| 524 | ||
| 525 | fn render_blank( | |
| 526 | gpa: std.mem.Allocator, | |
| 527 | bw: *Writer, | |
| 528 | defines: std.array_hash_map.String(Value), | |
| 529 | include_path: []const u8, | |
| 530 | include_guard_override: ?[]const u8, | |
| 531 | ) !void { | |
| 532 | const include_guard_name = include_guard_override orelse blk: { | |
| 533 | const name = try gpa.dupe(u8, include_path); | |
| 534 | for (name) |*byte| { | |
| 535 | switch (byte.*) { | |
| 536 | 'a'...'z' => byte.* = byte.* - 'a' + 'A', | |
| 537 | 'A'...'Z', '0'...'9' => continue, | |
| 538 | else => byte.* = '_', | |
| 539 | } | |
| 540 | } | |
| 541 | break :blk name; | |
| 542 | }; | |
| 543 | defer if (include_guard_override == null) gpa.free(include_guard_name); | |
| 544 | ||
| 545 | try bw.print( | |
| 546 | \\#ifndef {[0]s} | |
| 547 | \\#define {[0]s} | |
| 548 | \\ | |
| 549 | , .{include_guard_name}); | |
| 550 | ||
| 551 | const values = defines.values(); | |
| 552 | for (defines.keys(), 0..) |name, i| try renderValueC(bw, name, values[i]); | |
| 553 | ||
| 554 | try bw.print( | |
| 555 | \\#endif /* {s} */ | |
| 556 | \\ | |
| 557 | , .{include_guard_name}); | |
| 558 | } | |
| 559 | ||
| 560 | fn render_nasm(bw: *Writer, defines: std.array_hash_map.String(Value)) !void { | |
| 561 | for (defines.keys(), defines.values()) |name, value| try renderValueNasm(bw, name, value); | |
| 562 | } | |
| 563 | ||
| 564 | fn renderValueC(bw: *Writer, name: []const u8, value: Value) !void { | |
| 565 | switch (value) { | |
| 566 | .undef => try bw.print("/* #undef {s} */\n", .{name}), | |
| 567 | .defined => try bw.print("#define {s}\n", .{name}), | |
| 568 | .boolean => |b| try bw.print("#define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }), | |
| 569 | .int => |i| try bw.print("#define {s} {d}\n", .{ name, i }), | |
| 570 | .ident => |ident| try bw.print("#define {s} {s}\n", .{ name, ident }), | |
| 571 | // TODO: use C-specific escaping instead of zig string literals | |
| 572 | .string => |string| try bw.print("#define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }), | |
| 573 | } | |
| 574 | } | |
| 575 | ||
| 576 | fn renderValueNasm(bw: *Writer, name: []const u8, value: Value) !void { | |
| 577 | switch (value) { | |
| 578 | .undef => try bw.print("; %undef {s}\n", .{name}), | |
| 579 | .defined => try bw.print("%define {s}\n", .{name}), | |
| 580 | .boolean => |b| try bw.print("%define {s} {c}\n", .{ name, @as(u8, '0') + @intFromBool(b) }), | |
| 581 | .int => |i| try bw.print("%define {s} {d}\n", .{ name, i }), | |
| 582 | .ident => |ident| try bw.print("%define {s} {s}\n", .{ name, ident }), | |
| 583 | // TODO: use nasm-specific escaping instead of zig string literals | |
| 584 | .string => |string| try bw.print("%define {s} \"{f}\"\n", .{ name, std.zig.fmtString(string) }), | |
| 585 | } | |
| 586 | } | |
| 587 | ||
| 588 | fn expand_variables_autoconf_at( | |
| 589 | bw: *Writer, | |
| 590 | contents: []const u8, | |
| 591 | values: *const std.array_hash_map.String(Value), | |
| 592 | used: []bool, | |
| 593 | ) !void { | |
| 594 | const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; | |
| 595 | ||
| 596 | var curr: usize = 0; | |
| 597 | var source_offset: usize = 0; | |
| 598 | while (curr < contents.len) : (curr += 1) { | |
| 599 | if (contents[curr] != '@') continue; | |
| 600 | if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| { | |
| 601 | if (close_pos == curr + 1) { | |
| 602 | // closed immediately, preserve as a literal | |
| 603 | continue; | |
| 604 | } | |
| 605 | const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0; | |
| 606 | if (valid_varname_end != close_pos) { | |
| 607 | // contains invalid characters, preserve as a literal | |
| 608 | continue; | |
| 609 | } | |
| 610 | ||
| 611 | const key = contents[curr + 1 .. close_pos]; | |
| 612 | const index = values.getIndex(key) orelse { | |
| 613 | // Report the missing key to the caller. | |
| 614 | try bw.writeAll(key); | |
| 615 | return error.MissingValue; | |
| 616 | }; | |
| 617 | const value = values.entries.slice().items(.value)[index]; | |
| 618 | used[index] = true; | |
| 619 | try bw.writeAll(contents[source_offset..curr]); | |
| 620 | switch (value) { | |
| 621 | .undef, .defined => {}, | |
| 622 | .boolean => |b| try bw.writeByte(@as(u8, '0') + @intFromBool(b)), | |
| 623 | .int => |i| try bw.print("{d}", .{i}), | |
| 624 | .ident, .string => |s| try bw.writeAll(s), | |
| 625 | } | |
| 626 | ||
| 627 | curr = close_pos; | |
| 628 | source_offset = close_pos + 1; | |
| 629 | } | |
| 630 | } | |
| 631 | ||
| 632 | try bw.writeAll(contents[source_offset..]); | |
| 633 | } | |
| 634 | ||
| 635 | fn expand_variables_cmake( | |
| 636 | allocator: Allocator, | |
| 637 | contents: []const u8, | |
| 638 | values: std.array_hash_map.String(Value), | |
| 639 | ) ![]const u8 { | |
| 640 | var result: std.array_list.Managed(u8) = .init(allocator); | |
| 641 | errdefer result.deinit(); | |
| 642 | ||
| 643 | const valid_varname_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/_.+-"; | |
| 644 | const open_var = "${"; | |
| 645 | ||
| 646 | var curr: usize = 0; | |
| 647 | var source_offset: usize = 0; | |
| 648 | const Position = struct { | |
| 649 | source: usize, | |
| 650 | target: usize, | |
| 651 | }; | |
| 652 | var var_stack: std.array_list.Managed(Position) = .init(allocator); | |
| 653 | defer var_stack.deinit(); | |
| 654 | loop: while (curr < contents.len) : (curr += 1) { | |
| 655 | switch (contents[curr]) { | |
| 656 | '@' => blk: { | |
| 657 | if (std.mem.findScalarPos(u8, contents, curr + 1, '@')) |close_pos| { | |
| 658 | if (close_pos == curr + 1) { | |
| 659 | // closed immediately, preserve as a literal | |
| 660 | break :blk; | |
| 661 | } | |
| 662 | const valid_varname_end = std.mem.findNonePos(u8, contents, curr + 1, valid_varname_chars) orelse 0; | |
| 663 | if (valid_varname_end != close_pos) { | |
| 664 | // contains invalid characters, preserve as a literal | |
| 665 | break :blk; | |
| 666 | } | |
| 667 | ||
| 668 | const key = contents[curr + 1 .. close_pos]; | |
| 669 | const value = values.get(key) orelse return error.MissingValue; | |
| 670 | const missing = contents[source_offset..curr]; | |
| 671 | try result.appendSlice(missing); | |
| 672 | switch (value) { | |
| 673 | .undef, .defined => {}, | |
| 674 | .boolean => |b| { | |
| 675 | try result.append(if (b) '1' else '0'); | |
| 676 | }, | |
| 677 | .int => |i| { | |
| 678 | try result.print("{d}", .{i}); | |
| 679 | }, | |
| 680 | .ident, .string => |s| { | |
| 681 | try result.appendSlice(s); | |
| 682 | }, | |
| 683 | } | |
| 684 | ||
| 685 | curr = close_pos; | |
| 686 | source_offset = close_pos + 1; | |
| 687 | ||
| 688 | continue :loop; | |
| 689 | } | |
| 690 | }, | |
| 691 | '$' => blk: { | |
| 692 | const next = curr + 1; | |
| 693 | if (next == contents.len or contents[next] != '{') { | |
| 694 | // no open bracket detected, preserve as a literal | |
| 695 | break :blk; | |
| 696 | } | |
| 697 | const missing = contents[source_offset..curr]; | |
| 698 | try result.appendSlice(missing); | |
| 699 | try result.appendSlice(open_var); | |
| 700 | ||
| 701 | source_offset = curr + open_var.len; | |
| 702 | curr = next; | |
| 703 | try var_stack.append(Position{ | |
| 704 | .source = curr, | |
| 705 | .target = result.items.len - open_var.len, | |
| 706 | }); | |
| 707 | ||
| 708 | continue :loop; | |
| 709 | }, | |
| 710 | '}' => blk: { | |
| 711 | if (var_stack.items.len == 0) { | |
| 712 | // no open bracket, preserve as a literal | |
| 713 | break :blk; | |
| 714 | } | |
| 715 | const open_pos = var_stack.pop().?; | |
| 716 | if (source_offset == open_pos.source) { | |
| 717 | source_offset += open_var.len; | |
| 718 | } | |
| 719 | const missing = contents[source_offset..curr]; | |
| 720 | try result.appendSlice(missing); | |
| 721 | ||
| 722 | const key_start = open_pos.target + open_var.len; | |
| 723 | const key = result.items[key_start..]; | |
| 724 | if (key.len == 0) { | |
| 725 | return error.MissingKey; | |
| 726 | } | |
| 727 | const value = values.get(key) orelse return error.MissingValue; | |
| 728 | result.shrinkRetainingCapacity(result.items.len - key.len - open_var.len); | |
| 729 | switch (value) { | |
| 730 | .undef, .defined => {}, | |
| 731 | .boolean => |b| { | |
| 732 | try result.append(if (b) '1' else '0'); | |
| 733 | }, | |
| 734 | .int => |i| { | |
| 735 | try result.print("{d}", .{i}); | |
| 736 | }, | |
| 737 | .ident, .string => |s| { | |
| 738 | try result.appendSlice(s); | |
| 739 | }, | |
| 740 | } | |
| 741 | ||
| 742 | source_offset = curr + 1; | |
| 743 | ||
| 744 | continue :loop; | |
| 745 | }, | |
| 746 | '\\' => { | |
| 747 | // backslash is not considered a special character | |
| 748 | continue :loop; | |
| 749 | }, | |
| 750 | else => {}, | |
| 751 | } | |
| 752 | ||
| 753 | if (var_stack.items.len > 0 and std.mem.findScalar(u8, valid_varname_chars, contents[curr]) == null) { | |
| 754 | return error.InvalidCharacter; | |
| 755 | } | |
| 756 | } | |
| 757 | ||
| 758 | if (source_offset != contents.len) { | |
| 759 | const missing = contents[source_offset..]; | |
| 760 | try result.appendSlice(missing); | |
| 175 | pub fn addValues(config_header: *ConfigHeader, values: anytype) void { | |
| 176 | inline for (@typeInfo(@TypeOf(values)).@"struct".fields) |field| { | |
| 177 | addValue(config_header, field.name, field.type, @field(values, field.name)); | |
| 761 | 178 | } |
| 762 | ||
| 763 | return result.toOwnedSlice(); | |
| 764 | } | |
| 765 | ||
| 766 | fn testReplaceVariablesAutoconfAt( | |
| 767 | allocator: Allocator, | |
| 768 | contents: []const u8, | |
| 769 | expected: []const u8, | |
| 770 | values: std.array_hash_map.String(Value), | |
| 771 | ) !void { | |
| 772 | var aw: Writer.Allocating = .init(allocator); | |
| 773 | defer aw.deinit(); | |
| 774 | ||
| 775 | const used = try allocator.alloc(bool, values.count()); | |
| 776 | for (used) |*u| u.* = false; | |
| 777 | defer allocator.free(used); | |
| 778 | ||
| 779 | try expand_variables_autoconf_at(&aw.writer, contents, values, used); | |
| 780 | ||
| 781 | for (used) |u| if (!u) return error.UnusedValue; | |
| 782 | try std.testing.expectEqualStrings(expected, aw.written()); | |
| 783 | } | |
| 784 | ||
| 785 | fn testReplaceVariablesCMake( | |
| 786 | allocator: Allocator, | |
| 787 | contents: []const u8, | |
| 788 | expected: []const u8, | |
| 789 | values: std.array_hash_map.String(Value), | |
| 790 | ) !void { | |
| 791 | const actual = try expand_variables_cmake(allocator, contents, values); | |
| 792 | defer allocator.free(actual); | |
| 793 | ||
| 794 | try std.testing.expectEqualStrings(expected, actual); | |
| 795 | } | |
| 796 | ||
| 797 | test "expand_variables_autoconf_at simple cases" { | |
| 798 | const allocator = std.testing.allocator; | |
| 799 | var values: std.array_hash_map.String(Value) = .init(allocator); | |
| 800 | defer values.deinit(); | |
| 801 | ||
| 802 | // empty strings are preserved | |
| 803 | try testReplaceVariablesAutoconfAt(allocator, "", "", values); | |
| 804 | ||
| 805 | // line with misc content is preserved | |
| 806 | try testReplaceVariablesAutoconfAt(allocator, "no substitution", "no substitution", values); | |
| 807 | ||
| 808 | // empty @ sigils are preserved | |
| 809 | try testReplaceVariablesAutoconfAt(allocator, "@", "@", values); | |
| 810 | try testReplaceVariablesAutoconfAt(allocator, "@@", "@@", values); | |
| 811 | try testReplaceVariablesAutoconfAt(allocator, "@@@", "@@@", values); | |
| 812 | try testReplaceVariablesAutoconfAt(allocator, "@@@@", "@@@@", values); | |
| 813 | ||
| 814 | // simple substitution | |
| 815 | try values.putNoClobber("undef", .undef); | |
| 816 | try testReplaceVariablesAutoconfAt(allocator, "@undef@", "", values); | |
| 817 | values.clearRetainingCapacity(); | |
| 818 | ||
| 819 | try values.putNoClobber("defined", .defined); | |
| 820 | try testReplaceVariablesAutoconfAt(allocator, "@defined@", "", values); | |
| 821 | values.clearRetainingCapacity(); | |
| 822 | ||
| 823 | try values.putNoClobber("true", Value{ .boolean = true }); | |
| 824 | try testReplaceVariablesAutoconfAt(allocator, "@true@", "1", values); | |
| 825 | values.clearRetainingCapacity(); | |
| 826 | ||
| 827 | try values.putNoClobber("false", Value{ .boolean = false }); | |
| 828 | try testReplaceVariablesAutoconfAt(allocator, "@false@", "0", values); | |
| 829 | values.clearRetainingCapacity(); | |
| 830 | ||
| 831 | try values.putNoClobber("int", Value{ .int = 42 }); | |
| 832 | try testReplaceVariablesAutoconfAt(allocator, "@int@", "42", values); | |
| 833 | values.clearRetainingCapacity(); | |
| 834 | ||
| 835 | try values.putNoClobber("ident", Value{ .string = "value" }); | |
| 836 | try testReplaceVariablesAutoconfAt(allocator, "@ident@", "value", values); | |
| 837 | values.clearRetainingCapacity(); | |
| 838 | ||
| 839 | try values.putNoClobber("string", Value{ .string = "text" }); | |
| 840 | try testReplaceVariablesAutoconfAt(allocator, "@string@", "text", values); | |
| 841 | values.clearRetainingCapacity(); | |
| 842 | ||
| 843 | // double packed substitution | |
| 844 | try values.putNoClobber("string", Value{ .string = "text" }); | |
| 845 | try testReplaceVariablesAutoconfAt(allocator, "@string@@string@", "texttext", values); | |
| 846 | values.clearRetainingCapacity(); | |
| 847 | ||
| 848 | // triple packed substitution | |
| 849 | try values.putNoClobber("int", Value{ .int = 42 }); | |
| 850 | try values.putNoClobber("string", Value{ .string = "text" }); | |
| 851 | try testReplaceVariablesAutoconfAt(allocator, "@string@@int@@string@", "text42text", values); | |
| 852 | values.clearRetainingCapacity(); | |
| 853 | ||
| 854 | // double separated substitution | |
| 855 | try values.putNoClobber("int", Value{ .int = 42 }); | |
| 856 | try testReplaceVariablesAutoconfAt(allocator, "@int@.@int@", "42.42", values); | |
| 857 | values.clearRetainingCapacity(); | |
| 858 | ||
| 859 | // triple separated substitution | |
| 860 | try values.putNoClobber("true", Value{ .boolean = true }); | |
| 861 | try values.putNoClobber("int", Value{ .int = 42 }); | |
| 862 | try testReplaceVariablesAutoconfAt(allocator, "@int@.@true@.@int@", "42.1.42", values); | |
| 863 | values.clearRetainingCapacity(); | |
| 864 | ||
| 865 | // misc prefix is preserved | |
| 866 | try values.putNoClobber("false", Value{ .boolean = false }); | |
| 867 | try testReplaceVariablesAutoconfAt(allocator, "false is @false@", "false is 0", values); | |
| 868 | values.clearRetainingCapacity(); | |
| 869 | ||
| 870 | // misc suffix is preserved | |
| 871 | try values.putNoClobber("true", Value{ .boolean = true }); | |
| 872 | try testReplaceVariablesAutoconfAt(allocator, "@true@ is true", "1 is true", values); | |
| 873 | values.clearRetainingCapacity(); | |
| 874 | ||
| 875 | // surrounding content is preserved | |
| 876 | try values.putNoClobber("int", Value{ .int = 42 }); | |
| 877 | try testReplaceVariablesAutoconfAt(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values); | |
| 878 | values.clearRetainingCapacity(); | |
| 879 | ||
| 880 | // incomplete key is preserved | |
| 881 | try testReplaceVariablesAutoconfAt(allocator, "@undef", "@undef", values); | |
| 882 | ||
| 883 | // unknown key leads to an error | |
| 884 | try std.testing.expectError(error.MissingValue, testReplaceVariablesAutoconfAt(allocator, "@bad@", "", values)); | |
| 885 | ||
| 886 | // unused key leads to an error | |
| 887 | try values.putNoClobber("int", Value{ .int = 42 }); | |
| 888 | try values.putNoClobber("false", Value{ .boolean = false }); | |
| 889 | try std.testing.expectError(error.UnusedValue, testReplaceVariablesAutoconfAt(allocator, "@int", "", values)); | |
| 890 | values.clearRetainingCapacity(); | |
| 891 | } | |
| 892 | ||
| 893 | test "expand_variables_autoconf_at edge cases" { | |
| 894 | const allocator = std.testing.allocator; | |
| 895 | var values: std.array_hash_map.String(Value) = .init(allocator); | |
| 896 | defer values.deinit(); | |
| 897 | ||
| 898 | // @-vars resolved only when they wrap valid characters, otherwise considered literals | |
| 899 | try values.putNoClobber("string", Value{ .string = "text" }); | |
| 900 | try testReplaceVariablesAutoconfAt(allocator, "@@string@@", "@text@", values); | |
| 901 | values.clearRetainingCapacity(); | |
| 902 | ||
| 903 | // expanded variables are considered strings after expansion | |
| 904 | try values.putNoClobber("string_at", Value{ .string = "@string@" }); | |
| 905 | try testReplaceVariablesAutoconfAt(allocator, "@string_at@", "@string@", values); | |
| 906 | values.clearRetainingCapacity(); | |
| 907 | } | |
| 908 | ||
| 909 | test "expand_variables_cmake simple cases" { | |
| 910 | const allocator = std.testing.allocator; | |
| 911 | var values: std.array_hash_map.String(Value) = .init(allocator); | |
| 912 | defer values.deinit(); | |
| 913 | ||
| 914 | try values.putNoClobber("undef", .undef); | |
| 915 | try values.putNoClobber("defined", .defined); | |
| 916 | try values.putNoClobber("true", Value{ .boolean = true }); | |
| 917 | try values.putNoClobber("false", Value{ .boolean = false }); | |
| 918 | try values.putNoClobber("int", Value{ .int = 42 }); | |
| 919 | try values.putNoClobber("ident", Value{ .string = "value" }); | |
| 920 | try values.putNoClobber("string", Value{ .string = "text" }); | |
| 921 | ||
| 922 | // empty strings are preserved | |
| 923 | try testReplaceVariablesCMake(allocator, "", "", values); | |
| 924 | ||
| 925 | // line with misc content is preserved | |
| 926 | try testReplaceVariablesCMake(allocator, "no substitution", "no substitution", values); | |
| 927 | ||
| 928 | // empty ${} wrapper leads to an error | |
| 929 | try std.testing.expectError(error.MissingKey, testReplaceVariablesCMake(allocator, "${}", "", values)); | |
| 930 | ||
| 931 | // empty @ sigils are preserved | |
| 932 | try testReplaceVariablesCMake(allocator, "@", "@", values); | |
| 933 | try testReplaceVariablesCMake(allocator, "@@", "@@", values); | |
| 934 | try testReplaceVariablesCMake(allocator, "@@@", "@@@", values); | |
| 935 | try testReplaceVariablesCMake(allocator, "@@@@", "@@@@", values); | |
| 936 | ||
| 937 | // simple substitution | |
| 938 | try testReplaceVariablesCMake(allocator, "@undef@", "", values); | |
| 939 | try testReplaceVariablesCMake(allocator, "${undef}", "", values); | |
| 940 | try testReplaceVariablesCMake(allocator, "@defined@", "", values); | |
| 941 | try testReplaceVariablesCMake(allocator, "${defined}", "", values); | |
| 942 | try testReplaceVariablesCMake(allocator, "@true@", "1", values); | |
| 943 | try testReplaceVariablesCMake(allocator, "${true}", "1", values); | |
| 944 | try testReplaceVariablesCMake(allocator, "@false@", "0", values); | |
| 945 | try testReplaceVariablesCMake(allocator, "${false}", "0", values); | |
| 946 | try testReplaceVariablesCMake(allocator, "@int@", "42", values); | |
| 947 | try testReplaceVariablesCMake(allocator, "${int}", "42", values); | |
| 948 | try testReplaceVariablesCMake(allocator, "@ident@", "value", values); | |
| 949 | try testReplaceVariablesCMake(allocator, "${ident}", "value", values); | |
| 950 | try testReplaceVariablesCMake(allocator, "@string@", "text", values); | |
| 951 | try testReplaceVariablesCMake(allocator, "${string}", "text", values); | |
| 952 | ||
| 953 | // double packed substitution | |
| 954 | try testReplaceVariablesCMake(allocator, "@string@@string@", "texttext", values); | |
| 955 | try testReplaceVariablesCMake(allocator, "${string}${string}", "texttext", values); | |
| 956 | ||
| 957 | // triple packed substitution | |
| 958 | try testReplaceVariablesCMake(allocator, "@string@@int@@string@", "text42text", values); | |
| 959 | try testReplaceVariablesCMake(allocator, "@string@${int}@string@", "text42text", values); | |
| 960 | try testReplaceVariablesCMake(allocator, "${string}@int@${string}", "text42text", values); | |
| 961 | try testReplaceVariablesCMake(allocator, "${string}${int}${string}", "text42text", values); | |
| 962 | ||
| 963 | // double separated substitution | |
| 964 | try testReplaceVariablesCMake(allocator, "@int@.@int@", "42.42", values); | |
| 965 | try testReplaceVariablesCMake(allocator, "${int}.${int}", "42.42", values); | |
| 966 | ||
| 967 | // triple separated substitution | |
| 968 | try testReplaceVariablesCMake(allocator, "@int@.@true@.@int@", "42.1.42", values); | |
| 969 | try testReplaceVariablesCMake(allocator, "@int@.${true}.@int@", "42.1.42", values); | |
| 970 | try testReplaceVariablesCMake(allocator, "${int}.@true@.${int}", "42.1.42", values); | |
| 971 | try testReplaceVariablesCMake(allocator, "${int}.${true}.${int}", "42.1.42", values); | |
| 972 | ||
| 973 | // misc prefix is preserved | |
| 974 | try testReplaceVariablesCMake(allocator, "false is @false@", "false is 0", values); | |
| 975 | try testReplaceVariablesCMake(allocator, "false is ${false}", "false is 0", values); | |
| 976 | ||
| 977 | // misc suffix is preserved | |
| 978 | try testReplaceVariablesCMake(allocator, "@true@ is true", "1 is true", values); | |
| 979 | try testReplaceVariablesCMake(allocator, "${true} is true", "1 is true", values); | |
| 980 | ||
| 981 | // surrounding content is preserved | |
| 982 | try testReplaceVariablesCMake(allocator, "what is 6*7? @int@!", "what is 6*7? 42!", values); | |
| 983 | try testReplaceVariablesCMake(allocator, "what is 6*7? ${int}!", "what is 6*7? 42!", values); | |
| 984 | ||
| 985 | // incomplete key is preserved | |
| 986 | try testReplaceVariablesCMake(allocator, "@undef", "@undef", values); | |
| 987 | try testReplaceVariablesCMake(allocator, "${undef", "${undef", values); | |
| 988 | try testReplaceVariablesCMake(allocator, "{undef}", "{undef}", values); | |
| 989 | try testReplaceVariablesCMake(allocator, "undef@", "undef@", values); | |
| 990 | try testReplaceVariablesCMake(allocator, "undef}", "undef}", values); | |
| 991 | ||
| 992 | // unknown key leads to an error | |
| 993 | try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@bad@", "", values)); | |
| 994 | try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${bad}", "", values)); | |
| 995 | 179 | } |
| 996 | 180 | |
| 997 | test "expand_variables_cmake edge cases" { | |
| 998 | const allocator = std.testing.allocator; | |
| 999 | var values: std.array_hash_map.String(Value) = .init(allocator); | |
| 1000 | defer values.deinit(); | |
| 1001 | ||
| 1002 | // special symbols | |
| 1003 | try values.putNoClobber("at", Value{ .string = "@" }); | |
| 1004 | try values.putNoClobber("dollar", Value{ .string = "$" }); | |
| 1005 | try values.putNoClobber("underscore", Value{ .string = "_" }); | |
| 1006 | ||
| 1007 | // basic value | |
| 1008 | try values.putNoClobber("string", Value{ .string = "text" }); | |
| 1009 | ||
| 1010 | // proxy case values | |
| 1011 | try values.putNoClobber("string_proxy", Value{ .string = "string" }); | |
| 1012 | try values.putNoClobber("string_at", Value{ .string = "@string@" }); | |
| 1013 | try values.putNoClobber("string_curly", Value{ .string = "{string}" }); | |
| 1014 | try values.putNoClobber("string_var", Value{ .string = "${string}" }); | |
| 1015 | ||
| 1016 | // stack case values | |
| 1017 | try values.putNoClobber("nest_underscore_proxy", Value{ .string = "underscore" }); | |
| 1018 | try values.putNoClobber("nest_proxy", Value{ .string = "nest_underscore_proxy" }); | |
| 1019 | ||
| 1020 | // @-vars resolved only when they wrap valid characters, otherwise considered literals | |
| 1021 | try testReplaceVariablesCMake(allocator, "@@string@@", "@text@", values); | |
| 1022 | try testReplaceVariablesCMake(allocator, "@${string}@", "@text@", values); | |
| 1023 | ||
| 1024 | // @-vars are resolved inside ${}-vars | |
| 1025 | try testReplaceVariablesCMake(allocator, "${@string_proxy@}", "text", values); | |
| 1026 | ||
| 1027 | // expanded variables are considered strings after expansion | |
| 1028 | try testReplaceVariablesCMake(allocator, "@string_at@", "@string@", values); | |
| 1029 | try testReplaceVariablesCMake(allocator, "${string_at}", "@string@", values); | |
| 1030 | try testReplaceVariablesCMake(allocator, "$@string_curly@", "${string}", values); | |
| 1031 | try testReplaceVariablesCMake(allocator, "$${string_curly}", "${string}", values); | |
| 1032 | try testReplaceVariablesCMake(allocator, "${string_var}", "${string}", values); | |
| 1033 | try testReplaceVariablesCMake(allocator, "@string_var@", "${string}", values); | |
| 1034 | try testReplaceVariablesCMake(allocator, "${dollar}{${string}}", "${text}", values); | |
| 1035 | try testReplaceVariablesCMake(allocator, "@dollar@{${string}}", "${text}", values); | |
| 1036 | try testReplaceVariablesCMake(allocator, "@dollar@{@string@}", "${text}", values); | |
| 1037 | ||
| 1038 | // when expanded variables contain invalid characters, they prevent further expansion | |
| 1039 | try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${${string_var}}", "", values)); | |
| 1040 | try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${@string_var@}", "", values)); | |
| 1041 | ||
| 1042 | // nested expanded variables are expanded from the inside out | |
| 1043 | try testReplaceVariablesCMake(allocator, "${string${underscore}proxy}", "string", values); | |
| 1044 | try testReplaceVariablesCMake(allocator, "${string@underscore@proxy}", "string", values); | |
| 1045 | ||
| 1046 | // nested vars are only expanded when ${} is closed | |
| 1047 | try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@underscore@proxy@", "", values)); | |
| 1048 | try testReplaceVariablesCMake(allocator, "${nest${underscore}proxy}", "nest_underscore_proxy", values); | |
| 1049 | try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "@nest@@nest_underscore@underscore@proxy@@proxy@", "", values)); | |
| 1050 | try testReplaceVariablesCMake(allocator, "${nest${${nest_underscore${underscore}proxy}}proxy}", "nest_underscore_proxy", values); | |
| 1051 | ||
| 1052 | // invalid characters lead to an error | |
| 1053 | try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str*ing}", "", values)); | |
| 1054 | try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str$ing}", "", values)); | |
| 1055 | try std.testing.expectError(error.InvalidCharacter, testReplaceVariablesCMake(allocator, "${str@ing}", "", values)); | |
| 181 | pub fn getOutputDir(ch: *ConfigHeader) std.Build.LazyPath { | |
| 182 | return .{ .generated = .{ .index = ch.generated_dir } }; | |
| 1056 | 183 | } |
| 1057 | 184 | |
| 1058 | test "expand_variables_cmake escaped characters" { | |
| 1059 | const allocator = std.testing.allocator; | |
| 1060 | var values: std.array_hash_map.String(Value) = .init(allocator); | |
| 1061 | defer values.deinit(); | |
| 1062 | ||
| 1063 | try values.putNoClobber("string", Value{ .string = "text" }); | |
| 1064 | ||
| 1065 | // backslash is an invalid character for @ lookup | |
| 1066 | try testReplaceVariablesCMake(allocator, "\\@string\\@", "\\@string\\@", values); | |
| 1067 | ||
| 1068 | // backslash is preserved, but doesn't affect ${} variable expansion | |
| 1069 | try testReplaceVariablesCMake(allocator, "\\${string}", "\\text", values); | |
| 1070 | ||
| 1071 | // backslash breaks ${} opening bracket identification | |
| 1072 | try testReplaceVariablesCMake(allocator, "$\\{string}", "$\\{string}", values); | |
| 1073 | ||
| 1074 | // backslash is skipped when checking for invalid characters, yet it mangles the key | |
| 1075 | try std.testing.expectError(error.MissingValue, testReplaceVariablesCMake(allocator, "${string\\}", "", values)); | |
| 185 | pub fn getOutputFile(ch: *ConfigHeader) std.Build.LazyPath { | |
| 186 | return ch.getOutputDir().path(ch.step.owner, ch.include_path); | |
| 1076 | 187 | } |
lib/std/Build/Step/Fail.zig+10-20| ... | ... | @@ -1,35 +1,25 @@ |
| 1 | 1 | //! Fail the build with a given message. |
| 2 | const Fail = @This(); | |
| 3 | ||
| 2 | 4 | const std = @import("std"); |
| 3 | 5 | const Step = std.Build.Step; |
| 4 | const Fail = @This(); | |
| 6 | const Configuration = std.Build.Configuration; | |
| 5 | 7 | |
| 6 | 8 | step: Step, |
| 7 | error_msg: []const u8, | |
| 9 | error_msg: Configuration.String, | |
| 8 | 10 | |
| 9 | pub const base_id: Step.Id = .fail; | |
| 11 | pub const base_tag: Step.Tag = .fail; | |
| 10 | 12 | |
| 11 | 13 | pub fn create(owner: *std.Build, error_msg: []const u8) *Fail { |
| 12 | const fail = owner.allocator.create(Fail) catch @panic("OOM"); | |
| 13 | ||
| 14 | const graph = owner.graph; | |
| 15 | const fail = graph.create(Fail); | |
| 14 | 16 | fail.* = .{ |
| 15 | .step = Step.init(.{ | |
| 16 | .id = base_id, | |
| 17 | .step = .init(.{ | |
| 18 | .tag = base_tag, | |
| 17 | 19 | .name = "fail", |
| 18 | 20 | .owner = owner, |
| 19 | .makeFn = make, | |
| 20 | 21 | }), |
| 21 | .error_msg = owner.dupe(error_msg), | |
| 22 | .error_msg = graph.addString(error_msg), | |
| 22 | 23 | }; |
| 23 | ||
| 24 | 24 | return fail; |
| 25 | 25 | } |
| 26 | ||
| 27 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 28 | _ = options; // No progress to report. | |
| 29 | ||
| 30 | const fail: *Fail = @fieldParentPtr("step", step); | |
| 31 | ||
| 32 | try step.result_error_msgs.append(step.owner.allocator, fail.error_msg); | |
| 33 | ||
| 34 | return error.MakeFailed; | |
| 35 | } |
lib/std/Build/Step/FindProgram.zig created+31| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | const FindProgram = @This(); | |
| 2 | ||
| 3 | const std = @import("std"); | |
| 4 | const Step = std.Build.Step; | |
| 5 | const Configuration = std.Build.Configuration; | |
| 6 | ||
| 7 | step: Step, | |
| 8 | found_path: Configuration.GeneratedFileIndex, | |
| 9 | names: Configuration.StringList, | |
| 10 | ||
| 11 | pub const base_tag: Step.Tag = .find_program; | |
| 12 | ||
| 13 | pub const Options = struct { | |
| 14 | names: []const []const u8, | |
| 15 | }; | |
| 16 | ||
| 17 | pub fn create(owner: *std.Build, options: Options) *FindProgram { | |
| 18 | const graph = owner.graph; | |
| 19 | const wc = &graph.wip_configuration; | |
| 20 | const fp = graph.create(FindProgram); | |
| 21 | fp.* = .{ | |
| 22 | .step = .init(.{ | |
| 23 | .tag = base_tag, | |
| 24 | .name = owner.fmt("find program {s} ({d} candidates)", .{ options.names[0], options.names.len }), | |
| 25 | .owner = owner, | |
| 26 | }), | |
| 27 | .found_path = graph.addGeneratedFile(&fp.step), | |
| 28 | .names = wc.addStringList(options.names) catch @panic("OOM"), | |
| 29 | }; | |
| 30 | return fp; | |
| 31 | } |
lib/std/Build/Step/Fmt.zig+22-57| ... | ... | @@ -1,81 +1,46 @@ |
| 1 | 1 | //! This step has two modes: |
| 2 | 2 | //! * Modify mode: directly modify source files, formatting them in place. |
| 3 | 3 | //! * Check mode: fail the step if a non-conforming file is found. |
| 4 | const Fmt = @This(); | |
| 5 | ||
| 4 | 6 | const std = @import("std"); |
| 5 | 7 | const Step = std.Build.Step; |
| 6 | const Fmt = @This(); | |
| 8 | const LazyPath = std.Build.LazyPath; | |
| 9 | const Configuration = std.Build.Configuration; | |
| 7 | 10 | |
| 8 | 11 | step: Step, |
| 9 | paths: []const []const u8, | |
| 10 | exclude_paths: []const []const u8, | |
| 12 | /// Intended to be read-only after the `Fmt` step is created. | |
| 13 | paths: []const LazyPath, | |
| 14 | /// Intended to be read-only after the `Fmt` step is created. | |
| 15 | exclude_paths: []const LazyPath, | |
| 11 | 16 | check: bool, |
| 12 | 17 | |
| 13 | pub const base_id: Step.Id = .fmt; | |
| 18 | pub const base_tag: Step.Tag = .fmt; | |
| 14 | 19 | |
| 15 | 20 | pub const Options = struct { |
| 16 | paths: []const []const u8 = &.{}, | |
| 17 | exclude_paths: []const []const u8 = &.{}, | |
| 21 | paths: []const LazyPath = &.{}, | |
| 22 | exclude_paths: []const LazyPath = &.{}, | |
| 18 | 23 | /// If true, fails the build step when any non-conforming files are encountered. |
| 19 | 24 | check: bool = false, |
| 20 | 25 | }; |
| 21 | 26 | |
| 22 | 27 | pub fn create(owner: *std.Build, options: Options) *Fmt { |
| 23 | const fmt = owner.allocator.create(Fmt) catch @panic("OOM"); | |
| 24 | const name = if (options.check) "zig fmt --check" else "zig fmt"; | |
| 28 | const graph = owner.graph; | |
| 29 | const fmt = graph.create(Fmt); | |
| 30 | ||
| 25 | 31 | fmt.* = .{ |
| 26 | .step = Step.init(.{ | |
| 27 | .id = base_id, | |
| 28 | .name = name, | |
| 32 | .step = .init(.{ | |
| 33 | .tag = base_tag, | |
| 34 | .name = if (options.check) "zig fmt --check" else "zig fmt", | |
| 29 | 35 | .owner = owner, |
| 30 | .makeFn = make, | |
| 31 | 36 | }), |
| 32 | .paths = owner.dupeStrings(options.paths), | |
| 33 | .exclude_paths = owner.dupeStrings(options.exclude_paths), | |
| 37 | .paths = options.paths, | |
| 38 | .exclude_paths = options.exclude_paths, | |
| 34 | 39 | .check = options.check, |
| 35 | 40 | }; |
| 36 | return fmt; | |
| 37 | } | |
| 38 | ||
| 39 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 40 | const prog_node = options.progress_node; | |
| 41 | ||
| 42 | // TODO: if check=false, this means we are modifying source files in place, which | |
| 43 | // is an operation that could race against other operations also modifying source files | |
| 44 | // in place. In this case, this step should obtain a write lock while making those | |
| 45 | // modifications. | |
| 46 | 41 | |
| 47 | const b = step.owner; | |
| 48 | const arena = b.allocator; | |
| 49 | const fmt: *Fmt = @fieldParentPtr("step", step); | |
| 42 | for (options.paths) |lp| lp.addStepDependencies(&fmt.step); | |
| 43 | for (options.exclude_paths) |lp| lp.addStepDependencies(&fmt.step); | |
| 50 | 44 | |
| 51 | var argv: std.ArrayList([]const u8) = .empty; | |
| 52 | try argv.ensureUnusedCapacity(arena, 2 + 1 + fmt.paths.len + 2 * fmt.exclude_paths.len); | |
| 53 | ||
| 54 | argv.appendAssumeCapacity(b.graph.zig_exe); | |
| 55 | argv.appendAssumeCapacity("fmt"); | |
| 56 | ||
| 57 | if (fmt.check) { | |
| 58 | argv.appendAssumeCapacity("--check"); | |
| 59 | } | |
| 60 | ||
| 61 | for (fmt.paths) |p| { | |
| 62 | argv.appendAssumeCapacity(b.pathFromRoot(p)); | |
| 63 | } | |
| 64 | ||
| 65 | for (fmt.exclude_paths) |p| { | |
| 66 | argv.appendAssumeCapacity("--exclude"); | |
| 67 | argv.appendAssumeCapacity(b.pathFromRoot(p)); | |
| 68 | } | |
| 69 | ||
| 70 | const run_result = try step.captureChildProcess(options.gpa, prog_node, argv.items); | |
| 71 | if (fmt.check) switch (run_result.term) { | |
| 72 | .exited => |code| if (code != 0 and run_result.stdout.len != 0) { | |
| 73 | var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n'); | |
| 74 | while (it.next()) |bad_file_name| { | |
| 75 | try step.addError("{s}: non-conforming formatting", .{bad_file_name}); | |
| 76 | } | |
| 77 | }, | |
| 78 | else => {}, | |
| 79 | }; | |
| 80 | try step.handleChildProcessTerm(run_result.term); | |
| 45 | return fmt; | |
| 81 | 46 | } |
lib/std/Build/Step/InstallArtifact.zig+26-141| ... | ... | @@ -1,14 +1,14 @@ |
| 1 | const InstallArtifact = @This(); | |
| 2 | ||
| 1 | 3 | const std = @import("std"); |
| 2 | 4 | const Step = std.Build.Step; |
| 3 | 5 | const InstallDir = std.Build.InstallDir; |
| 4 | const InstallArtifact = @This(); | |
| 5 | const fs = std.fs; | |
| 6 | 6 | const LazyPath = std.Build.LazyPath; |
| 7 | 7 | |
| 8 | 8 | step: Step, |
| 9 | 9 | |
| 10 | 10 | dest_dir: ?InstallDir, |
| 11 | dest_sub_path: []const u8, | |
| 11 | dest_sub_path: ?[]const u8, | |
| 12 | 12 | emitted_bin: ?LazyPath, |
| 13 | 13 | |
| 14 | 14 | implib_dir: ?InstallDir, |
| ... | ... | @@ -17,14 +17,10 @@ emitted_implib: ?LazyPath, |
| 17 | 17 | pdb_dir: ?InstallDir, |
| 18 | 18 | emitted_pdb: ?LazyPath, |
| 19 | 19 | |
| 20 | // hack for stage2_x86_64 + coff | |
| 21 | compiler_rt_dyn_lib_dir: ?InstallDir, | |
| 22 | emitted_compiler_rt_dyn_lib: ?LazyPath, | |
| 23 | ||
| 24 | 20 | h_dir: ?InstallDir, |
| 25 | 21 | emitted_h: ?LazyPath, |
| 26 | 22 | |
| 27 | dylib_symlinks: ?DylibSymlinkInfo, | |
| 23 | dylib_symlinks: bool, | |
| 28 | 24 | |
| 29 | 25 | artifact: *Step.Compile, |
| 30 | 26 | |
| ... | ... | @@ -33,7 +29,7 @@ const DylibSymlinkInfo = struct { |
| 33 | 29 | name_only_filename: []const u8, |
| 34 | 30 | }; |
| 35 | 31 | |
| 36 | pub const base_id: Step.Id = .install_artifact; | |
| 32 | pub const base_tag: Step.Tag = .install_artifact; | |
| 37 | 33 | |
| 38 | 34 | pub const Options = struct { |
| 39 | 35 | /// Which installation directory to put the main output file into. |
| ... | ... | @@ -67,158 +63,47 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins |
| 67 | 63 | }, |
| 68 | 64 | .override => |o| o, |
| 69 | 65 | }; |
| 66 | const pdb_dir: ?InstallDir = switch (options.pdb_dir) { | |
| 67 | .disabled => null, | |
| 68 | .default => if (artifact.producesPdbFile()) dest_dir else null, | |
| 69 | .override => |o| o, | |
| 70 | }; | |
| 71 | const implib_dir: ?InstallDir = switch (options.implib_dir) { | |
| 72 | .disabled => null, | |
| 73 | .default => if (artifact.producesImplib()) .lib else null, | |
| 74 | .override => |o| o, | |
| 75 | }; | |
| 70 | 76 | install_artifact.* = .{ |
| 71 | 77 | .step = Step.init(.{ |
| 72 | .id = base_id, | |
| 78 | .tag = base_tag, | |
| 73 | 79 | .name = owner.fmt("install {s}", .{artifact.name}), |
| 74 | 80 | .owner = owner, |
| 75 | .makeFn = make, | |
| 76 | 81 | }), |
| 77 | 82 | .dest_dir = dest_dir, |
| 78 | .pdb_dir = switch (options.pdb_dir) { | |
| 79 | .disabled => null, | |
| 80 | .default => if (artifact.producesPdbFile()) dest_dir else null, | |
| 81 | .override => |o| o, | |
| 82 | }, | |
| 83 | .compiler_rt_dyn_lib_dir = switch (options.compiler_rt_dyn_lib_dir) { | |
| 84 | .disabled => null, | |
| 85 | .default => if (artifact.producesCompilerRtDynLib()) dest_dir else null, | |
| 86 | .override => |o| o, | |
| 87 | }, | |
| 83 | .pdb_dir = pdb_dir, | |
| 88 | 84 | .h_dir = switch (options.h_dir) { |
| 89 | 85 | .disabled => null, |
| 90 | 86 | .default => if (artifact.kind == .lib) .header else null, |
| 91 | 87 | .override => |o| o, |
| 92 | 88 | }, |
| 93 | .implib_dir = switch (options.implib_dir) { | |
| 94 | .disabled => null, | |
| 95 | .default => if (artifact.producesImplib()) .lib else null, | |
| 96 | .override => |o| o, | |
| 97 | }, | |
| 89 | .implib_dir = implib_dir, | |
| 98 | 90 | |
| 99 | .dylib_symlinks = if (options.dylib_symlinks orelse (dest_dir != null and | |
| 100 | artifact.isDynamicLibrary() and | |
| 101 | artifact.version != null and | |
| 102 | std.Build.wantSharedLibSymLinks(artifact.rootModuleTarget()))) .{ | |
| 103 | .major_only_filename = artifact.major_only_filename.?, | |
| 104 | .name_only_filename = artifact.name_only_filename.?, | |
| 105 | } else null, | |
| 91 | .dylib_symlinks = options.dylib_symlinks orelse (dest_dir != null and | |
| 92 | artifact.isDynamicLibrary() and artifact.version != null and | |
| 93 | std.Build.wantSharedLibSymLinks(artifact.rootModuleTarget())), | |
| 106 | 94 | |
| 107 | .dest_sub_path = options.dest_sub_path orelse artifact.out_filename, | |
| 95 | .dest_sub_path = options.dest_sub_path, | |
| 108 | 96 | |
| 109 | .emitted_bin = null, | |
| 110 | .emitted_pdb = null, | |
| 111 | .emitted_compiler_rt_dyn_lib = null, | |
| 97 | .emitted_bin = if (dest_dir != null) artifact.getEmittedBin() else null, | |
| 98 | .emitted_pdb = if (pdb_dir != null) artifact.getEmittedPdb() else null, | |
| 99 | // https://github.com/ziglang/zig/issues/9698 | |
| 112 | 100 | .emitted_h = null, |
| 113 | .emitted_implib = null, | |
| 101 | .emitted_implib = if (implib_dir != null) artifact.getEmittedImplib() else null, | |
| 114 | 102 | |
| 115 | 103 | .artifact = artifact, |
| 116 | 104 | }; |
| 117 | 105 | |
| 118 | 106 | install_artifact.step.dependOn(&artifact.step); |
| 119 | 107 | |
| 120 | if (install_artifact.dest_dir != null) install_artifact.emitted_bin = artifact.getEmittedBin(); | |
| 121 | if (install_artifact.compiler_rt_dyn_lib_dir != null) install_artifact.emitted_compiler_rt_dyn_lib = artifact.getEmittedCompilerRtDynLib(); | |
| 122 | if (install_artifact.pdb_dir != null) install_artifact.emitted_pdb = artifact.getEmittedPdb(); | |
| 123 | // https://github.com/ziglang/zig/issues/9698 | |
| 124 | //if (install_artifact.h_dir != null) install_artifact.emitted_h = artifact.getEmittedH(); | |
| 125 | if (install_artifact.implib_dir != null) install_artifact.emitted_implib = artifact.getEmittedImplib(); | |
| 126 | ||
| 127 | 108 | return install_artifact; |
| 128 | 109 | } |
| 129 | ||
| 130 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 131 | _ = options; | |
| 132 | const install_artifact: *InstallArtifact = @fieldParentPtr("step", step); | |
| 133 | const b = step.owner; | |
| 134 | const io = b.graph.io; | |
| 135 | ||
| 136 | var all_cached = true; | |
| 137 | ||
| 138 | if (install_artifact.dest_dir) |dest_dir| { | |
| 139 | const full_dest_path = b.getInstallPath(dest_dir, install_artifact.dest_sub_path); | |
| 140 | const p = try step.installFile(install_artifact.emitted_bin.?, full_dest_path); | |
| 141 | all_cached = all_cached and p == .fresh; | |
| 142 | ||
| 143 | if (install_artifact.dylib_symlinks) |dls| { | |
| 144 | try Step.Compile.doAtomicSymLinks(step, full_dest_path, dls.major_only_filename, dls.name_only_filename); | |
| 145 | } | |
| 146 | ||
| 147 | install_artifact.artifact.installed_path = full_dest_path; | |
| 148 | } | |
| 149 | ||
| 150 | if (install_artifact.compiler_rt_dyn_lib_dir) |compiler_rt_dir| { | |
| 151 | const full_compiler_rt_path = b.getInstallPath(compiler_rt_dir, install_artifact.emitted_compiler_rt_dyn_lib.?.basename(b, step)); | |
| 152 | const p = try step.installFile(install_artifact.emitted_compiler_rt_dyn_lib.?, full_compiler_rt_path); | |
| 153 | all_cached = all_cached and p == .fresh; | |
| 154 | } | |
| 155 | ||
| 156 | if (install_artifact.implib_dir) |implib_dir| { | |
| 157 | const full_implib_path = b.getInstallPath(implib_dir, install_artifact.emitted_implib.?.basename(b, step)); | |
| 158 | const p = try step.installFile(install_artifact.emitted_implib.?, full_implib_path); | |
| 159 | all_cached = all_cached and p == .fresh; | |
| 160 | } | |
| 161 | ||
| 162 | if (install_artifact.pdb_dir) |pdb_dir| { | |
| 163 | const full_pdb_path = b.getInstallPath(pdb_dir, install_artifact.emitted_pdb.?.basename(b, step)); | |
| 164 | const p = try step.installFile(install_artifact.emitted_pdb.?, full_pdb_path); | |
| 165 | all_cached = all_cached and p == .fresh; | |
| 166 | } | |
| 167 | ||
| 168 | if (install_artifact.h_dir) |h_dir| { | |
| 169 | if (install_artifact.emitted_h) |emitted_h| { | |
| 170 | const full_h_path = b.getInstallPath(h_dir, emitted_h.basename(b, step)); | |
| 171 | const p = try step.installFile(emitted_h, full_h_path); | |
| 172 | all_cached = all_cached and p == .fresh; | |
| 173 | } | |
| 174 | ||
| 175 | for (install_artifact.artifact.installed_headers.items) |installation| switch (installation) { | |
| 176 | .file => |file| { | |
| 177 | const full_h_path = b.getInstallPath(h_dir, file.dest_rel_path); | |
| 178 | const p = try step.installFile(file.source, full_h_path); | |
| 179 | all_cached = all_cached and p == .fresh; | |
| 180 | }, | |
| 181 | .directory => |dir| { | |
| 182 | const src_dir_path = dir.source.getPath3(b, step); | |
| 183 | const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path); | |
| 184 | ||
| 185 | var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { | |
| 186 | return step.fail("unable to open source directory '{f}': {s}", .{ | |
| 187 | src_dir_path, @errorName(err), | |
| 188 | }); | |
| 189 | }; | |
| 190 | defer src_dir.close(io); | |
| 191 | ||
| 192 | var it = try src_dir.walk(b.allocator); | |
| 193 | next_entry: while (try it.next(io)) |entry| { | |
| 194 | for (dir.options.exclude_extensions) |ext| { | |
| 195 | if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry; | |
| 196 | } | |
| 197 | if (dir.options.include_extensions) |incs| { | |
| 198 | for (incs) |inc| { | |
| 199 | if (std.mem.endsWith(u8, entry.path, inc)) break; | |
| 200 | } else { | |
| 201 | continue :next_entry; | |
| 202 | } | |
| 203 | } | |
| 204 | ||
| 205 | const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path }); | |
| 206 | switch (entry.kind) { | |
| 207 | .directory => { | |
| 208 | try Step.handleVerbose(b, .inherit, &.{ "install", "-d", full_dest_path }); | |
| 209 | const p = try step.installDir(full_dest_path); | |
| 210 | all_cached = all_cached and p == .existed; | |
| 211 | }, | |
| 212 | .file => { | |
| 213 | const p = try step.installFile(try dir.source.join(b.allocator, entry.path), full_dest_path); | |
| 214 | all_cached = all_cached and p == .fresh; | |
| 215 | }, | |
| 216 | else => continue, | |
| 217 | } | |
| 218 | } | |
| 219 | }, | |
| 220 | }; | |
| 221 | } | |
| 222 | ||
| 223 | step.result_cached = all_cached; | |
| 224 | } |
lib/std/Build/Step/InstallDir.zig+14-67| ... | ... | @@ -1,14 +1,15 @@ |
| 1 | const InstallDir = @This(); | |
| 2 | ||
| 1 | 3 | const std = @import("std"); |
| 2 | 4 | const mem = std.mem; |
| 3 | 5 | const fs = std.fs; |
| 4 | 6 | const Step = std.Build.Step; |
| 5 | 7 | const LazyPath = std.Build.LazyPath; |
| 6 | const InstallDir = @This(); | |
| 7 | 8 | |
| 8 | 9 | step: Step, |
| 9 | 10 | options: Options, |
| 10 | 11 | |
| 11 | pub const base_id: Step.Id = .install_dir; | |
| 12 | pub const base_tag: Step.Tag = .install_dir; | |
| 12 | 13 | |
| 13 | 14 | pub const Options = struct { |
| 14 | 15 | source_dir: LazyPath, |
| ... | ... | @@ -28,83 +29,29 @@ pub const Options = struct { |
| 28 | 29 | /// `@import("test.zig")` would be a compile error. |
| 29 | 30 | blank_extensions: []const []const u8 = &.{}, |
| 30 | 31 | |
| 31 | fn dupe(opts: Options, b: *std.Build) Options { | |
| 32 | fn dupe(opts: Options, graph: *const std.Build.Graph) Options { | |
| 32 | 33 | return .{ |
| 33 | .source_dir = opts.source_dir.dupe(b), | |
| 34 | .install_dir = opts.install_dir.dupe(b), | |
| 35 | .install_subdir = b.dupe(opts.install_subdir), | |
| 36 | .exclude_extensions = b.dupeStrings(opts.exclude_extensions), | |
| 37 | .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null, | |
| 38 | .blank_extensions = b.dupeStrings(opts.blank_extensions), | |
| 34 | .source_dir = opts.source_dir.dupe(graph), | |
| 35 | .install_dir = opts.install_dir.dupe(graph), | |
| 36 | .install_subdir = graph.dupeString(opts.install_subdir), | |
| 37 | .exclude_extensions = graph.dupeStrings(opts.exclude_extensions), | |
| 38 | .include_extensions = if (opts.include_extensions) |incs| graph.dupeStrings(incs) else null, | |
| 39 | .blank_extensions = graph.dupeStrings(opts.blank_extensions), | |
| 39 | 40 | }; |
| 40 | 41 | } |
| 41 | 42 | }; |
| 42 | 43 | |
| 43 | 44 | pub fn create(owner: *std.Build, options: Options) *InstallDir { |
| 44 | 45 | const install_dir = owner.allocator.create(InstallDir) catch @panic("OOM"); |
| 46 | const graph = owner.graph; | |
| 45 | 47 | install_dir.* = .{ |
| 46 | 48 | .step = Step.init(.{ |
| 47 | .id = base_id, | |
| 48 | .name = owner.fmt("install {s}/", .{options.source_dir.getDisplayName()}), | |
| 49 | .tag = base_tag, | |
| 50 | .name = owner.fmt("install {f}/", .{options.source_dir}), | |
| 49 | 51 | .owner = owner, |
| 50 | .makeFn = make, | |
| 51 | 52 | }), |
| 52 | .options = options.dupe(owner), | |
| 53 | .options = options.dupe(graph), | |
| 53 | 54 | }; |
| 54 | 55 | options.source_dir.addStepDependencies(&install_dir.step); |
| 55 | 56 | return install_dir; |
| 56 | 57 | } |
| 57 | ||
| 58 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 59 | _ = options; | |
| 60 | const b = step.owner; | |
| 61 | const io = b.graph.io; | |
| 62 | const install_dir: *InstallDir = @fieldParentPtr("step", step); | |
| 63 | step.clearWatchInputs(); | |
| 64 | const arena = b.allocator; | |
| 65 | const dest_prefix = b.getInstallPath(install_dir.options.install_dir, install_dir.options.install_subdir); | |
| 66 | const src_dir_path = install_dir.options.source_dir.getPath3(b, step); | |
| 67 | const need_derived_inputs = try step.addDirectoryWatchInput(install_dir.options.source_dir); | |
| 68 | var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { | |
| 69 | return step.fail("unable to open source directory '{f}': {t}", .{ src_dir_path, err }); | |
| 70 | }; | |
| 71 | defer src_dir.close(io); | |
| 72 | var it = try src_dir.walk(arena); | |
| 73 | var all_cached = true; | |
| 74 | next_entry: while (try it.next(io)) |entry| { | |
| 75 | for (install_dir.options.exclude_extensions) |ext| { | |
| 76 | if (mem.endsWith(u8, entry.path, ext)) continue :next_entry; | |
| 77 | } | |
| 78 | if (install_dir.options.include_extensions) |incs| { | |
| 79 | for (incs) |inc| { | |
| 80 | if (mem.endsWith(u8, entry.path, inc)) break; | |
| 81 | } else { | |
| 82 | continue :next_entry; | |
| 83 | } | |
| 84 | } | |
| 85 | ||
| 86 | const src_path = try install_dir.options.source_dir.join(b.allocator, entry.path); | |
| 87 | const dest_path = b.pathJoin(&.{ dest_prefix, entry.path }); | |
| 88 | switch (entry.kind) { | |
| 89 | .directory => { | |
| 90 | if (need_derived_inputs) _ = try step.addDirectoryWatchInput(src_path); | |
| 91 | const p = try step.installDir(dest_path); | |
| 92 | all_cached = all_cached and p == .existed; | |
| 93 | }, | |
| 94 | .file => { | |
| 95 | for (install_dir.options.blank_extensions) |ext| { | |
| 96 | if (mem.endsWith(u8, entry.path, ext)) { | |
| 97 | try b.truncateFile(dest_path); | |
| 98 | continue :next_entry; | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | const p = try step.installFile(src_path, dest_path); | |
| 103 | all_cached = all_cached and p == .fresh; | |
| 104 | }, | |
| 105 | else => continue, | |
| 106 | } | |
| 107 | } | |
| 108 | ||
| 109 | step.result_cached = all_cached; | |
| 110 | } |
lib/std/Build/Step/InstallFile.zig+12-21| ... | ... | @@ -1,17 +1,18 @@ |
| 1 | const InstallFile = @This(); | |
| 2 | ||
| 1 | 3 | const std = @import("std"); |
| 2 | 4 | const Step = std.Build.Step; |
| 3 | 5 | const LazyPath = std.Build.LazyPath; |
| 4 | 6 | const InstallDir = std.Build.InstallDir; |
| 5 | const InstallFile = @This(); | |
| 6 | 7 | const assert = std.debug.assert; |
| 7 | 8 | |
| 8 | pub const base_id: Step.Id = .install_file; | |
| 9 | ||
| 10 | 9 | step: Step, |
| 11 | 10 | source: LazyPath, |
| 12 | 11 | dir: InstallDir, |
| 13 | 12 | dest_rel_path: []const u8, |
| 14 | 13 | |
| 14 | pub const base_tag: Step.Tag = .install_file; | |
| 15 | ||
| 15 | 16 | pub fn create( |
| 16 | 17 | owner: *std.Build, |
| 17 | 18 | source: LazyPath, |
| ... | ... | @@ -19,29 +20,19 @@ pub fn create( |
| 19 | 20 | dest_rel_path: []const u8, |
| 20 | 21 | ) *InstallFile { |
| 21 | 22 | assert(dest_rel_path.len != 0); |
| 22 | const install_file = owner.allocator.create(InstallFile) catch @panic("OOM"); | |
| 23 | const graph = owner.graph; | |
| 24 | const arena = graph.arena; | |
| 25 | const install_file = arena.create(InstallFile) catch @panic("OOM"); | |
| 23 | 26 | install_file.* = .{ |
| 24 | 27 | .step = Step.init(.{ |
| 25 | .id = base_id, | |
| 26 | .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), | |
| 28 | .tag = base_tag, | |
| 29 | .name = owner.fmt("install {f} to {s}", .{ source, dest_rel_path }), | |
| 27 | 30 | .owner = owner, |
| 28 | .makeFn = make, | |
| 29 | 31 | }), |
| 30 | .source = source.dupe(owner), | |
| 31 | .dir = dir.dupe(owner), | |
| 32 | .dest_rel_path = owner.dupePath(dest_rel_path), | |
| 32 | .source = source.dupe(graph), | |
| 33 | .dir = dir.dupe(graph), | |
| 34 | .dest_rel_path = graph.dupePath(dest_rel_path), | |
| 33 | 35 | }; |
| 34 | 36 | source.addStepDependencies(&install_file.step); |
| 35 | 37 | return install_file; |
| 36 | 38 | } |
| 37 | ||
| 38 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 39 | _ = options; | |
| 40 | const b = step.owner; | |
| 41 | const install_file: *InstallFile = @fieldParentPtr("step", step); | |
| 42 | try step.singleUnchangingWatchInput(install_file.source); | |
| 43 | ||
| 44 | const full_dest_path = b.getInstallPath(install_file.dir, install_file.dest_rel_path); | |
| 45 | const p = try step.installFile(install_file.source, full_dest_path); | |
| 46 | step.result_cached = p == .fresh; | |
| 47 | } |
lib/std/Build/Step/ObjCopy.zig+82-199| ... | ... | @@ -1,92 +1,43 @@ |
| 1 | const std = @import("std"); | |
| 2 | 1 | const ObjCopy = @This(); |
| 3 | 2 | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | const ArenaAllocator = std.heap.ArenaAllocator; | |
| 6 | const File = std.Io.File; | |
| 7 | const InstallDir = std.Build.InstallDir; | |
| 3 | const std = @import("std"); | |
| 8 | 4 | const Step = std.Build.Step; |
| 9 | const elf = std.elf; | |
| 10 | const fs = std.fs; | |
| 11 | const sort = std.sort; | |
| 12 | ||
| 13 | pub const base_id: Step.Id = .objcopy; | |
| 14 | ||
| 15 | pub const RawFormat = enum { | |
| 16 | bin, | |
| 17 | hex, | |
| 18 | elf, | |
| 19 | }; | |
| 20 | ||
| 21 | pub const Strip = enum { | |
| 22 | none, | |
| 23 | debug, | |
| 24 | debug_and_symbols, | |
| 25 | }; | |
| 26 | ||
| 27 | pub const SectionFlags = packed struct { | |
| 28 | /// add SHF_ALLOC | |
| 29 | alloc: bool = false, | |
| 30 | ||
| 31 | /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing | |
| 32 | contents: bool = false, | |
| 33 | ||
| 34 | /// if section is SHT_NOBITS, set SHT_PROGBITS, otherwise do nothing (same as contents) | |
| 35 | load: bool = false, | |
| 5 | const Configuration = std.Build.Configuration; | |
| 36 | 6 | |
| 37 | /// readonly: clear default SHF_WRITE flag | |
| 38 | readonly: bool = false, | |
| 39 | ||
| 40 | /// add SHF_EXECINSTR | |
| 41 | code: bool = false, | |
| 7 | step: Step, | |
| 8 | input_file: std.Build.LazyPath, | |
| 9 | basename: Configuration.OptionalString, | |
| 10 | output_file: Configuration.GeneratedFileIndex, | |
| 11 | debug_file: ?DebugFile, | |
| 42 | 12 | |
| 43 | /// add SHF_EXCLUDE | |
| 44 | exclude: bool = false, | |
| 13 | format: ?Format, | |
| 14 | only_section: Configuration.OptionalString, | |
| 15 | pad_to: ?u64, | |
| 16 | strip: Strip, | |
| 17 | compress_debug: bool, | |
| 45 | 18 | |
| 46 | /// add SHF_X86_64_LARGE. Fatal error if target is not x86_64 | |
| 47 | large: bool = false, | |
| 19 | add_sections: std.ArrayList(AddSection) = .empty, | |
| 20 | update_sections: std.ArrayList(Configuration.Step.ObjCopy.UpdateSection) = .empty, | |
| 48 | 21 | |
| 49 | /// add SHF_MERGE | |
| 50 | merge: bool = false, | |
| 22 | pub const base_tag: Step.Tag = .obj_copy; | |
| 51 | 23 | |
| 52 | /// add SHF_STRINGS | |
| 53 | strings: bool = false, | |
| 54 | }; | |
| 24 | pub const Format = enum { binary, hex, elf }; | |
| 25 | pub const Strip = Configuration.Step.ObjCopy.Strip; | |
| 26 | pub const SectionFlags = Configuration.Step.ObjCopy.SectionFlags; | |
| 55 | 27 | |
| 56 | 28 | pub const AddSection = struct { |
| 57 | section_name: []const u8, | |
| 29 | section_name: Configuration.String, | |
| 58 | 30 | file_path: std.Build.LazyPath, |
| 59 | 31 | }; |
| 60 | 32 | |
| 61 | pub const SetSectionAlignment = struct { | |
| 62 | section_name: []const u8, | |
| 63 | alignment: u32, | |
| 64 | }; | |
| 65 | ||
| 66 | pub const SetSectionFlags = struct { | |
| 67 | section_name: []const u8, | |
| 68 | flags: SectionFlags, | |
| 33 | pub const DebugFile = struct { | |
| 34 | basename: Configuration.OptionalString, | |
| 35 | output_file: Configuration.GeneratedFileIndex, | |
| 69 | 36 | }; |
| 70 | 37 | |
| 71 | step: Step, | |
| 72 | input_file: std.Build.LazyPath, | |
| 73 | basename: []const u8, | |
| 74 | output_file: std.Build.GeneratedFile, | |
| 75 | output_file_debug: ?std.Build.GeneratedFile, | |
| 76 | ||
| 77 | format: ?RawFormat, | |
| 78 | only_section: ?[]const u8, | |
| 79 | pad_to: ?u64, | |
| 80 | strip: Strip, | |
| 81 | compress_debug: bool, | |
| 82 | ||
| 83 | add_section: ?AddSection, | |
| 84 | set_section_alignment: ?SetSectionAlignment, | |
| 85 | set_section_flags: ?SetSectionFlags, | |
| 86 | ||
| 87 | 38 | pub const Options = struct { |
| 88 | 39 | basename: ?[]const u8 = null, |
| 89 | format: ?RawFormat = null, | |
| 40 | format: ?Format = null, | |
| 90 | 41 | only_section: ?[]const u8 = null, |
| 91 | 42 | pad_to: ?u64 = null, |
| 92 | 43 | |
| ... | ... | @@ -96,148 +47,80 @@ pub const Options = struct { |
| 96 | 47 | /// Put the stripped out debug sections in a separate file. |
| 97 | 48 | /// note: the `basename` is baked into the elf file to specify the link to the separate debug file. |
| 98 | 49 | /// see https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html |
| 99 | extract_to_separate_file: bool = false, | |
| 50 | /// | |
| 51 | /// Makes `getOutputSeparatedDebug` return non-null. | |
| 52 | separate_debug_file: ?SeparateDebugFile = null, | |
| 100 | 53 | |
| 101 | add_section: ?AddSection = null, | |
| 102 | set_section_alignment: ?SetSectionAlignment = null, | |
| 103 | set_section_flags: ?SetSectionFlags = null, | |
| 54 | pub const SeparateDebugFile = struct { | |
| 55 | basename: ?[]const u8, | |
| 56 | }; | |
| 104 | 57 | }; |
| 105 | 58 | |
| 106 | pub fn create( | |
| 107 | owner: *std.Build, | |
| 108 | input_file: std.Build.LazyPath, | |
| 109 | options: Options, | |
| 110 | ) *ObjCopy { | |
| 111 | const objcopy = owner.allocator.create(ObjCopy) catch @panic("OOM"); | |
| 112 | objcopy.* = ObjCopy{ | |
| 113 | .step = Step.init(.{ | |
| 114 | .id = base_id, | |
| 115 | .name = owner.fmt("objcopy {s}", .{input_file.getDisplayName()}), | |
| 59 | pub fn create(owner: *std.Build, input_file: std.Build.LazyPath, options: Options) *ObjCopy { | |
| 60 | const graph = owner.graph; | |
| 61 | const wc = &graph.wip_configuration; | |
| 62 | const oc = graph.create(ObjCopy); | |
| 63 | oc.* = .{ | |
| 64 | .step = .init(.{ | |
| 65 | .tag = base_tag, | |
| 66 | .name = owner.fmt("objcopy {f}", .{input_file}), | |
| 116 | 67 | .owner = owner, |
| 117 | .makeFn = make, | |
| 118 | 68 | }), |
| 119 | 69 | .input_file = input_file, |
| 120 | .basename = options.basename orelse input_file.getDisplayName(), | |
| 121 | .output_file = std.Build.GeneratedFile{ .step = &objcopy.step }, | |
| 122 | .output_file_debug = if (options.strip != .none and options.extract_to_separate_file) std.Build.GeneratedFile{ .step = &objcopy.step } else null, | |
| 70 | .basename = if (options.basename) |s| .init(wc.addString(s) catch @panic("OOM")) else .none, | |
| 71 | .output_file = graph.addGeneratedFile(&oc.step), | |
| 72 | .debug_file = if (options.separate_debug_file) |df| .{ | |
| 73 | .basename = if (df.basename) |s| .init(wc.addString(s) catch @panic("OOM")) else .none, | |
| 74 | .output_file = graph.addGeneratedFile(&oc.step), | |
| 75 | } else null, | |
| 123 | 76 | .format = options.format, |
| 124 | .only_section = options.only_section, | |
| 77 | .only_section = if (options.only_section) |s| .init(wc.addString(s) catch @panic("OOM")) else .none, | |
| 125 | 78 | .pad_to = options.pad_to, |
| 126 | 79 | .strip = options.strip, |
| 127 | 80 | .compress_debug = options.compress_debug, |
| 128 | .add_section = options.add_section, | |
| 129 | .set_section_alignment = options.set_section_alignment, | |
| 130 | .set_section_flags = options.set_section_flags, | |
| 131 | 81 | }; |
| 132 | input_file.addStepDependencies(&objcopy.step); | |
| 133 | return objcopy; | |
| 82 | input_file.addStepDependencies(&oc.step); | |
| 83 | return oc; | |
| 134 | 84 | } |
| 135 | 85 | |
| 136 | pub fn getOutput(objcopy: *const ObjCopy) std.Build.LazyPath { | |
| 137 | return .{ .generated = .{ .file = &objcopy.output_file } }; | |
| 86 | pub const UpdateSectionOptions = struct { | |
| 87 | alignment: ?std.mem.Alignment = null, | |
| 88 | flags: SectionFlags = .default, | |
| 89 | }; | |
| 90 | ||
| 91 | pub fn updateSection(oc: *ObjCopy, section_name: []const u8, options: UpdateSectionOptions) void { | |
| 92 | const graph = oc.owner.graph; | |
| 93 | const arena = graph.arena; | |
| 94 | const wc = &graph.wip_configuration; | |
| 95 | oc.update_sections.append(arena, .{ | |
| 96 | .flags = .{ | |
| 97 | .section_flags = options.flags, | |
| 98 | .alignment = .init(options.alignment), | |
| 99 | }, | |
| 100 | .section_name = wc.addString(section_name) catch @panic("OOM"), | |
| 101 | }) catch @panic("OOM"); | |
| 138 | 102 | } |
| 139 | pub fn getOutputSeparatedDebug(objcopy: *const ObjCopy) ?std.Build.LazyPath { | |
| 140 | return if (objcopy.output_file_debug) |*file| .{ .generated = .{ .file = file } } else null; | |
| 103 | ||
| 104 | pub const AddSectionOptions = struct { | |
| 105 | file_path: std.Build.LazyPath, | |
| 106 | }; | |
| 107 | ||
| 108 | pub fn addSection(oc: *ObjCopy, section_name: []const u8, options: AddSectionOptions) void { | |
| 109 | const graph = oc.owner.graph; | |
| 110 | const arena = graph.arena; | |
| 111 | const wc = &graph.wip_configuration; | |
| 112 | oc.add_sections.append(arena, .{ | |
| 113 | .section_name = wc.addString(section_name) catch @panic("OOM"), | |
| 114 | .file_path = options.file_path, | |
| 115 | }) catch @panic("OOM"); | |
| 116 | options.file_path.addStepDependencies(&oc.step); | |
| 141 | 117 | } |
| 142 | 118 | |
| 143 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 144 | const prog_node = options.progress_node; | |
| 145 | const b = step.owner; | |
| 146 | const io = b.graph.io; | |
| 147 | const objcopy: *ObjCopy = @fieldParentPtr("step", step); | |
| 148 | try step.singleUnchangingWatchInput(objcopy.input_file); | |
| 149 | ||
| 150 | var man = b.graph.cache.obtain(); | |
| 151 | defer man.deinit(); | |
| 152 | ||
| 153 | const full_src_path = objcopy.input_file.getPath2(b, step); | |
| 154 | _ = try man.addFile(full_src_path, null); | |
| 155 | man.hash.addOptionalBytes(objcopy.only_section); | |
| 156 | man.hash.addOptional(objcopy.pad_to); | |
| 157 | man.hash.addOptional(objcopy.format); | |
| 158 | man.hash.add(objcopy.compress_debug); | |
| 159 | man.hash.add(objcopy.strip); | |
| 160 | man.hash.add(objcopy.output_file_debug != null); | |
| 161 | ||
| 162 | if (try step.cacheHit(&man)) { | |
| 163 | // Cache hit, skip subprocess execution. | |
| 164 | const digest = man.final(); | |
| 165 | objcopy.output_file.path = try b.cache_root.join(b.allocator, &.{ | |
| 166 | "o", &digest, objcopy.basename, | |
| 167 | }); | |
| 168 | if (objcopy.output_file_debug) |*file| { | |
| 169 | file.path = try b.cache_root.join(b.allocator, &.{ | |
| 170 | "o", &digest, b.fmt("{s}.debug", .{objcopy.basename}), | |
| 171 | }); | |
| 172 | } | |
| 173 | return; | |
| 174 | } | |
| 175 | ||
| 176 | const digest = man.final(); | |
| 177 | const cache_path = "o" ++ fs.path.sep_str ++ digest; | |
| 178 | const full_dest_path = try b.cache_root.join(b.allocator, &.{ cache_path, objcopy.basename }); | |
| 179 | const full_dest_path_debug = try b.cache_root.join(b.allocator, &.{ cache_path, b.fmt("{s}.debug", .{objcopy.basename}) }); | |
| 180 | b.cache_root.handle.createDirPath(io, cache_path) catch |err| { | |
| 181 | return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); | |
| 182 | }; | |
| 119 | pub fn getOutput(oc: *const ObjCopy) std.Build.LazyPath { | |
| 120 | return .{ .generated = .{ .index = oc.output_file } }; | |
| 121 | } | |
| 183 | 122 | |
| 184 | var argv = std.array_list.Managed([]const u8).init(b.allocator); | |
| 185 | try argv.appendSlice(&.{ b.graph.zig_exe, "objcopy" }); | |
| 186 | ||
| 187 | if (objcopy.only_section) |only_section| { | |
| 188 | try argv.appendSlice(&.{ "-j", only_section }); | |
| 189 | } | |
| 190 | switch (objcopy.strip) { | |
| 191 | .none => {}, | |
| 192 | .debug => try argv.appendSlice(&.{"--strip-debug"}), | |
| 193 | .debug_and_symbols => try argv.appendSlice(&.{"--strip-all"}), | |
| 194 | } | |
| 195 | if (objcopy.pad_to) |pad_to| { | |
| 196 | try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) }); | |
| 197 | } | |
| 198 | if (objcopy.format) |format| switch (format) { | |
| 199 | .bin => try argv.appendSlice(&.{ "-O", "binary" }), | |
| 200 | .hex => try argv.appendSlice(&.{ "-O", "hex" }), | |
| 201 | .elf => try argv.appendSlice(&.{ "-O", "elf" }), | |
| 202 | }; | |
| 203 | if (objcopy.compress_debug) { | |
| 204 | try argv.appendSlice(&.{"--compress-debug-sections"}); | |
| 205 | } | |
| 206 | if (objcopy.output_file_debug != null) { | |
| 207 | try argv.appendSlice(&.{b.fmt("--extract-to={s}", .{full_dest_path_debug})}); | |
| 208 | } | |
| 209 | if (objcopy.add_section) |section| { | |
| 210 | try argv.append("--add-section"); | |
| 211 | try argv.appendSlice(&.{b.fmt("{s}={s}", .{ section.section_name, section.file_path.getPath2(b, step) })}); | |
| 212 | } | |
| 213 | if (objcopy.set_section_alignment) |set_align| { | |
| 214 | try argv.append("--set-section-alignment"); | |
| 215 | try argv.appendSlice(&.{b.fmt("{s}={d}", .{ set_align.section_name, set_align.alignment })}); | |
| 216 | } | |
| 217 | if (objcopy.set_section_flags) |set_flags| { | |
| 218 | const f = set_flags.flags; | |
| 219 | // trailing comma is allowed | |
| 220 | try argv.append("--set-section-flags"); | |
| 221 | try argv.appendSlice(&.{b.fmt("{s}={s}{s}{s}{s}{s}{s}{s}{s}{s}", .{ | |
| 222 | set_flags.section_name, | |
| 223 | if (f.alloc) "alloc," else "", | |
| 224 | if (f.contents) "contents," else "", | |
| 225 | if (f.load) "load," else "", | |
| 226 | if (f.readonly) "readonly," else "", | |
| 227 | if (f.code) "code," else "", | |
| 228 | if (f.exclude) "exclude," else "", | |
| 229 | if (f.large) "large," else "", | |
| 230 | if (f.merge) "merge," else "", | |
| 231 | if (f.strings) "strings," else "", | |
| 232 | })}); | |
| 233 | } | |
| 234 | ||
| 235 | try argv.appendSlice(&.{ full_src_path, full_dest_path }); | |
| 236 | ||
| 237 | try argv.append("--listen=-"); | |
| 238 | _ = try step.evalZigProcess(argv.items, prog_node, false, options.web_server, options.gpa); | |
| 239 | ||
| 240 | objcopy.output_file.path = full_dest_path; | |
| 241 | if (objcopy.output_file_debug) |*file| file.path = full_dest_path_debug; | |
| 242 | try man.writeManifest(); | |
| 123 | pub fn getOutputSeparatedDebug(oc: *const ObjCopy) ?std.Build.LazyPath { | |
| 124 | const df = oc.debug_file orelse return null; | |
| 125 | return .{ .generated = .{ .index = df.output_file } }; | |
| 243 | 126 | } |
lib/std/Build/Step/Options.zig+26-261| ... | ... | @@ -1,37 +1,41 @@ |
| 1 | 1 | const Options = @This(); |
| 2 | ||
| 2 | 3 | const builtin = @import("builtin"); |
| 3 | 4 | |
| 4 | 5 | const std = @import("std"); |
| 5 | 6 | const Io = std.Io; |
| 6 | 7 | const fs = std.fs; |
| 7 | 8 | const Step = std.Build.Step; |
| 8 | const GeneratedFile = std.Build.GeneratedFile; | |
| 9 | 9 | const LazyPath = std.Build.LazyPath; |
| 10 | ||
| 11 | pub const base_id: Step.Id = .options; | |
| 10 | const Configuration = std.Build.Configuration; | |
| 12 | 11 | |
| 13 | 12 | step: Step, |
| 14 | generated_file: GeneratedFile, | |
| 15 | ||
| 16 | contents: std.ArrayList(u8), | |
| 17 | args: std.ArrayList(Arg), | |
| 13 | generated_file: Configuration.GeneratedFileIndex, | |
| 14 | contents: std.ArrayList(u8) = .empty, | |
| 15 | args: std.ArrayList(Arg) = .empty, | |
| 18 | 16 | encountered_types: std.StringHashMapUnmanaged(void), |
| 19 | 17 | |
| 18 | pub const base_tag: Step.Tag = .options; | |
| 19 | ||
| 20 | pub const Arg = struct { | |
| 21 | name: Configuration.String, | |
| 22 | path: LazyPath, | |
| 23 | }; | |
| 24 | ||
| 20 | 25 | pub fn create(owner: *std.Build) *Options { |
| 21 | const options = owner.allocator.create(Options) catch @panic("OOM"); | |
| 26 | const graph = owner.graph; | |
| 27 | const arena = graph.arena; | |
| 28 | ||
| 29 | const options = arena.create(Options) catch @panic("OOM"); | |
| 22 | 30 | options.* = .{ |
| 23 | 31 | .step = .init(.{ |
| 24 | .id = base_id, | |
| 32 | .tag = base_tag, | |
| 25 | 33 | .name = "options", |
| 26 | 34 | .owner = owner, |
| 27 | .makeFn = make, | |
| 28 | 35 | }), |
| 29 | .generated_file = undefined, | |
| 30 | .contents = .empty, | |
| 31 | .args = .empty, | |
| 36 | .generated_file = graph.addGeneratedFile(&options.step), | |
| 32 | 37 | .encountered_types = .empty, |
| 33 | 38 | }; |
| 34 | options.generated_file = .{ .step = &options.step }; | |
| 35 | 39 | |
| 36 | 40 | return options; |
| 37 | 41 | } |
| ... | ... | @@ -410,16 +414,14 @@ fn printStructValue( |
| 410 | 414 | } |
| 411 | 415 | } |
| 412 | 416 | |
| 413 | /// The value is the path in the cache dir. | |
| 414 | /// Adds a dependency automatically. | |
| 415 | pub fn addOptionPath( | |
| 416 | options: *Options, | |
| 417 | name: []const u8, | |
| 418 | path: LazyPath, | |
| 419 | ) void { | |
| 420 | const arena = options.step.owner.allocator; | |
| 417 | /// The added option has type `[]const u8` and value of the provided path. | |
| 418 | pub fn addOptionPath(options: *Options, name: []const u8, path: LazyPath) void { | |
| 419 | const graph = options.step.owner.graph; | |
| 420 | const arena = graph.arena; | |
| 421 | const wc = &graph.wip_configuration; | |
| 422 | ||
| 421 | 423 | options.args.append(arena, .{ |
| 422 | .name = options.step.owner.dupe(name), | |
| 424 | .name = try wc.addString(name), | |
| 423 | 425 | .path = path.dupe(options.step.owner), |
| 424 | 426 | }) catch @panic("OOM"); |
| 425 | 427 | path.addStepDependencies(&options.step); |
| ... | ... | @@ -434,242 +436,5 @@ pub fn createModule(options: *Options) *std.Build.Module { |
| 434 | 436 | /// Returns the main artifact of this Build Step which is a Zig source file |
| 435 | 437 | /// generated from the key-value pairs of the Options. |
| 436 | 438 | pub fn getOutput(options: *Options) LazyPath { |
| 437 | return .{ .generated = .{ .file = &options.generated_file } }; | |
| 438 | } | |
| 439 | ||
| 440 | fn make(step: *Step, make_options: Step.MakeOptions) !void { | |
| 441 | // This step completes so quickly that no progress reporting is necessary. | |
| 442 | _ = make_options; | |
| 443 | ||
| 444 | const b = step.owner; | |
| 445 | const io = b.graph.io; | |
| 446 | const options: *Options = @fieldParentPtr("step", step); | |
| 447 | ||
| 448 | for (options.args.items) |item| { | |
| 449 | options.addOption( | |
| 450 | []const u8, | |
| 451 | item.name, | |
| 452 | item.path.getPath2(b, step), | |
| 453 | ); | |
| 454 | } | |
| 455 | if (!step.inputs.populated()) for (options.args.items) |item| { | |
| 456 | try step.addWatchInput(item.path); | |
| 457 | }; | |
| 458 | ||
| 459 | const basename = "options.zig"; | |
| 460 | ||
| 461 | // Hash contents to file name. | |
| 462 | var hash = b.graph.cache.hash; | |
| 463 | // Random bytes to make unique. Refresh this with new random bytes when | |
| 464 | // implementation is modified in a non-backwards-compatible way. | |
| 465 | hash.add(@as(u32, 0xad95e922)); | |
| 466 | hash.addBytes(options.contents.items); | |
| 467 | const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename; | |
| 468 | ||
| 469 | options.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path}); | |
| 470 | ||
| 471 | // Optimize for the hot path. Stat the file, and if it already exists, | |
| 472 | // cache hit. | |
| 473 | if (b.cache_root.handle.access(io, sub_path, .{})) |_| { | |
| 474 | // This is the hot path, success. | |
| 475 | step.result_cached = true; | |
| 476 | return; | |
| 477 | } else |outer_err| switch (outer_err) { | |
| 478 | error.FileNotFound => { | |
| 479 | var atomic_file = b.cache_root.handle.createFileAtomic(io, sub_path, .{ | |
| 480 | .replace = false, | |
| 481 | .make_path = true, | |
| 482 | }) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{ | |
| 483 | b.cache_root, sub_path, err, | |
| 484 | }); | |
| 485 | defer atomic_file.deinit(io); | |
| 486 | ||
| 487 | atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| { | |
| 488 | return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{ | |
| 489 | b.cache_root, sub_path, err, | |
| 490 | }); | |
| 491 | }; | |
| 492 | ||
| 493 | atomic_file.link(io) catch |err| switch (err) { | |
| 494 | error.PathAlreadyExists => { | |
| 495 | step.result_cached = true; | |
| 496 | return; | |
| 497 | }, | |
| 498 | else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{ | |
| 499 | b.cache_root, sub_path, err, | |
| 500 | }), | |
| 501 | }; | |
| 502 | }, | |
| 503 | else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{ | |
| 504 | b.cache_root, sub_path, e, | |
| 505 | }), | |
| 506 | } | |
| 507 | } | |
| 508 | ||
| 509 | const Arg = struct { | |
| 510 | name: []const u8, | |
| 511 | path: LazyPath, | |
| 512 | }; | |
| 513 | ||
| 514 | test Options { | |
| 515 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | |
| 516 | ||
| 517 | const io = std.testing.io; | |
| 518 | ||
| 519 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); | |
| 520 | defer arena.deinit(); | |
| 521 | ||
| 522 | const cwd = try std.process.currentPathAlloc(io, std.testing.allocator); | |
| 523 | defer std.testing.allocator.free(cwd); | |
| 524 | ||
| 525 | var graph: std.Build.Graph = .{ | |
| 526 | .io = io, | |
| 527 | .arena = arena.allocator(), | |
| 528 | .cache = .{ | |
| 529 | .io = io, | |
| 530 | .gpa = arena.allocator(), | |
| 531 | .manifest_dir = Io.Dir.cwd(), | |
| 532 | .cwd = cwd, | |
| 533 | }, | |
| 534 | .zig_exe = "test", | |
| 535 | .environ_map = std.process.Environ.Map.init(arena.allocator()), | |
| 536 | .global_cache_root = .{ .path = "test", .handle = Io.Dir.cwd() }, | |
| 537 | .host = .{ | |
| 538 | .query = .{}, | |
| 539 | .result = try std.zig.system.resolveTargetQuery(io, .{}), | |
| 540 | }, | |
| 541 | .zig_lib_directory = std.Build.Cache.Directory.cwd(), | |
| 542 | .time_report = false, | |
| 543 | }; | |
| 544 | ||
| 545 | var builder = try std.Build.create( | |
| 546 | &graph, | |
| 547 | .{ .path = "test", .handle = Io.Dir.cwd() }, | |
| 548 | .{ .path = "test", .handle = Io.Dir.cwd() }, | |
| 549 | &.{}, | |
| 550 | ); | |
| 551 | ||
| 552 | const options = builder.addOptions(); | |
| 553 | ||
| 554 | const KeywordEnum = enum { | |
| 555 | @"0.8.1", | |
| 556 | }; | |
| 557 | ||
| 558 | const NormalEnum = enum { | |
| 559 | foo, | |
| 560 | bar, | |
| 561 | }; | |
| 562 | ||
| 563 | const nested_array = [2][2]u16{ | |
| 564 | [2]u16{ 300, 200 }, | |
| 565 | [2]u16{ 300, 200 }, | |
| 566 | }; | |
| 567 | const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] }; | |
| 568 | ||
| 569 | const NormalStruct = struct { | |
| 570 | hello: ?[]const u8, | |
| 571 | world: bool = true, | |
| 572 | }; | |
| 573 | ||
| 574 | const NestedStruct = struct { | |
| 575 | normal_struct: NormalStruct, | |
| 576 | normal_enum: NormalEnum = .foo, | |
| 577 | }; | |
| 578 | ||
| 579 | options.addOption(usize, "option1", 1); | |
| 580 | options.addOption(?usize, "option2", null); | |
| 581 | options.addOption(?usize, "option3", 3); | |
| 582 | options.addOption(comptime_int, "option4", 4); | |
| 583 | options.addOption(comptime_float, "option5", 5.01); | |
| 584 | options.addOption([]const u8, "string", "zigisthebest"); | |
| 585 | options.addOption(?[]const u8, "optional_string", null); | |
| 586 | options.addOption([2][2]u16, "nested_array", nested_array); | |
| 587 | options.addOption([]const []const u16, "nested_slice", nested_slice); | |
| 588 | options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1"); | |
| 589 | options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar")); | |
| 590 | options.addOption(NormalEnum, "normal1_enum", NormalEnum.foo); | |
| 591 | options.addOption(NormalEnum, "normal2_enum", NormalEnum.bar); | |
| 592 | options.addOption(NormalStruct, "normal1_struct", NormalStruct{ | |
| 593 | .hello = "foo", | |
| 594 | }); | |
| 595 | options.addOption(NormalStruct, "normal2_struct", NormalStruct{ | |
| 596 | .hello = null, | |
| 597 | .world = false, | |
| 598 | }); | |
| 599 | options.addOption(NestedStruct, "nested_struct", NestedStruct{ | |
| 600 | .normal_struct = .{ .hello = "bar" }, | |
| 601 | }); | |
| 602 | ||
| 603 | try std.testing.expectEqualStrings( | |
| 604 | \\pub const option1: usize = 1; | |
| 605 | \\pub const option2: ?usize = null; | |
| 606 | \\pub const option3: ?usize = 3; | |
| 607 | \\pub const option4: comptime_int = 4; | |
| 608 | \\pub const option5: comptime_float = 5.01; | |
| 609 | \\pub const string: []const u8 = "zigisthebest"; | |
| 610 | \\pub const optional_string: ?[]const u8 = null; | |
| 611 | \\pub const nested_array: [2][2]u16 = [2][2]u16 { | |
| 612 | \\ [2]u16 { | |
| 613 | \\ 300, | |
| 614 | \\ 200, | |
| 615 | \\ }, | |
| 616 | \\ [2]u16 { | |
| 617 | \\ 300, | |
| 618 | \\ 200, | |
| 619 | \\ }, | |
| 620 | \\}; | |
| 621 | \\pub const nested_slice: []const []const u16 = &[_][]const u16 { | |
| 622 | \\ &[_]u16 { | |
| 623 | \\ 300, | |
| 624 | \\ 200, | |
| 625 | \\ }, | |
| 626 | \\ &[_]u16 { | |
| 627 | \\ 300, | |
| 628 | \\ 200, | |
| 629 | \\ }, | |
| 630 | \\}; | |
| 631 | \\pub const @"Build.Step.Options.decltest.Options.KeywordEnum" = enum (u0) { | |
| 632 | \\ @"0.8.1" = 0, | |
| 633 | \\}; | |
| 634 | \\pub const keyword_enum: @"Build.Step.Options.decltest.Options.KeywordEnum" = .@"0.8.1"; | |
| 635 | \\pub const semantic_version: @import("std").SemanticVersion = .{ | |
| 636 | \\ .major = 0, | |
| 637 | \\ .minor = 1, | |
| 638 | \\ .patch = 2, | |
| 639 | \\ .pre = "foo", | |
| 640 | \\ .build = "bar", | |
| 641 | \\}; | |
| 642 | \\pub const @"Build.Step.Options.decltest.Options.NormalEnum" = enum (u1) { | |
| 643 | \\ foo = 0, | |
| 644 | \\ bar = 1, | |
| 645 | \\}; | |
| 646 | \\pub const normal1_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo; | |
| 647 | \\pub const normal2_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .bar; | |
| 648 | \\pub const @"Build.Step.Options.decltest.Options.NormalStruct" = struct { | |
| 649 | \\ hello: ?[]const u8, | |
| 650 | \\ world: bool = true, | |
| 651 | \\}; | |
| 652 | \\pub const normal1_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{ | |
| 653 | \\ .hello = "foo", | |
| 654 | \\ .world = true, | |
| 655 | \\}; | |
| 656 | \\pub const normal2_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{ | |
| 657 | \\ .hello = null, | |
| 658 | \\ .world = false, | |
| 659 | \\}; | |
| 660 | \\pub const @"Build.Step.Options.decltest.Options.NestedStruct" = struct { | |
| 661 | \\ normal_struct: @"Build.Step.Options.decltest.Options.NormalStruct", | |
| 662 | \\ normal_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo, | |
| 663 | \\}; | |
| 664 | \\pub const nested_struct: @"Build.Step.Options.decltest.Options.NestedStruct" = .{ | |
| 665 | \\ .normal_struct = .{ | |
| 666 | \\ .hello = "bar", | |
| 667 | \\ .world = true, | |
| 668 | \\ }, | |
| 669 | \\ .normal_enum = .foo, | |
| 670 | \\}; | |
| 671 | \\ | |
| 672 | , options.contents.items); | |
| 673 | ||
| 674 | _ = try std.zig.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(arena.allocator(), 0), .zig); | |
| 439 | return .{ .generated = .{ .index = options.generated_file } }; | |
| 675 | 440 | } |
lib/std/Build/Step/Run.zig+139-2221| ... | ... | @@ -11,8 +11,9 @@ const process = std.process; |
| 11 | 11 | const EnvMap = std.process.Environ.Map; |
| 12 | 12 | const assert = std.debug.assert; |
| 13 | 13 | const Path = std.Build.Cache.Path; |
| 14 | const Configuration = std.Build.Configuration; | |
| 14 | 15 | |
| 15 | pub const base_id: Step.Id = .run; | |
| 16 | pub const base_tag: Step.Tag = .run; | |
| 16 | 17 | |
| 17 | 18 | step: Step, |
| 18 | 19 | |
| ... | ... | @@ -84,36 +85,13 @@ stdio_limit: std.Io.Limit, |
| 84 | 85 | captured_stdout: ?*CapturedStdIo, |
| 85 | 86 | captured_stderr: ?*CapturedStdIo, |
| 86 | 87 | |
| 87 | dep_output_file: ?*Output, | |
| 88 | ||
| 89 | 88 | has_side_effects: bool, |
| 90 | ||
| 91 | /// If this is a Zig unit test binary, this tracks the names of the unit | |
| 92 | /// tests that are also fuzz tests. Indexes cannot be used as they may | |
| 93 | /// change between reruns. | |
| 94 | fuzz_tests: std.ArrayList([]const u8), | |
| 95 | cached_test_metadata: ?CachedTestMetadata = null, | |
| 96 | ||
| 97 | /// Populated during the fuzz phase if this run step corresponds to a unit test | |
| 98 | /// executable that contains fuzz tests. | |
| 99 | rebuilt_executable: ?Path, | |
| 89 | test_runner_mode: bool = false, | |
| 100 | 90 | |
| 101 | 91 | /// If this Run step was produced by a Compile step, it is tracked here. |
| 102 | 92 | producer: ?*Step.Compile, |
| 103 | 93 | |
| 104 | pub const Color = enum { | |
| 105 | /// `CLICOLOR_FORCE` is set, and `NO_COLOR` is unset. | |
| 106 | enable, | |
| 107 | /// `NO_COLOR` is set, and `CLICOLOR_FORCE` is unset. | |
| 108 | disable, | |
| 109 | /// If the build runner is using color, equivalent to `.enable`. Otherwise, equivalent to `.disable`. | |
| 110 | inherit, | |
| 111 | /// If stderr is captured or checked, equivalent to `.disable`. Otherwise, equivalent to `.inherit`. | |
| 112 | auto, | |
| 113 | /// The build runner does not modify the `CLICOLOR_FORCE` or `NO_COLOR` environment variables. | |
| 114 | /// They are treated like normal variables, so can be controlled through `setEnvironmentVariable`. | |
| 115 | manual, | |
| 116 | }; | |
| 94 | pub const Color = std.Build.Configuration.Step.Run.Color; | |
| 117 | 95 | |
| 118 | 96 | pub const StdIn = union(enum) { |
| 119 | 97 | none, |
| ... | ... | @@ -159,9 +137,12 @@ pub const Arg = union(enum) { |
| 159 | 137 | lazy_path: PrefixedLazyPath, |
| 160 | 138 | decorated_directory: DecoratedLazyPath, |
| 161 | 139 | file_content: PrefixedLazyPath, |
| 162 | bytes: []u8, | |
| 140 | bytes: []const u8, | |
| 163 | 141 | output_file: *Output, |
| 142 | output_file_dep: *Output, | |
| 164 | 143 | output_directory: *Output, |
| 144 | /// The arguments passed after "--" on the "zig build" CLI. | |
| 145 | passthru, | |
| 165 | 146 | }; |
| 166 | 147 | |
| 167 | 148 | pub const PrefixedArtifact = struct { |
| ... | ... | @@ -181,7 +162,7 @@ pub const DecoratedLazyPath = struct { |
| 181 | 162 | }; |
| 182 | 163 | |
| 183 | 164 | pub const Output = struct { |
| 184 | generated_file: std.Build.GeneratedFile, | |
| 165 | generated_file: Configuration.GeneratedFileIndex, | |
| 185 | 166 | prefix: []const u8, |
| 186 | 167 | basename: []const u8, |
| 187 | 168 | }; |
| ... | ... | @@ -197,22 +178,16 @@ pub const CapturedStdIo = struct { |
| 197 | 178 | trim_whitespace: TrimWhitespace = .none, |
| 198 | 179 | }; |
| 199 | 180 | |
| 200 | pub const TrimWhitespace = enum { | |
| 201 | none, | |
| 202 | all, | |
| 203 | leading, | |
| 204 | trailing, | |
| 205 | }; | |
| 181 | pub const TrimWhitespace = std.Build.Configuration.Step.Run.TrimWhitespace; | |
| 206 | 182 | }; |
| 207 | 183 | |
| 208 | 184 | pub fn create(owner: *std.Build, name: []const u8) *Run { |
| 209 | 185 | const run = owner.allocator.create(Run) catch @panic("OOM"); |
| 210 | 186 | run.* = .{ |
| 211 | 187 | .step = .init(.{ |
| 212 | .id = base_id, | |
| 188 | .tag = base_tag, | |
| 213 | 189 | .name = name, |
| 214 | 190 | .owner = owner, |
| 215 | .makeFn = make, | |
| 216 | 191 | }), |
| 217 | 192 | .argv = .empty, |
| 218 | 193 | .cwd = null, |
| ... | ... | @@ -227,10 +202,7 @@ pub fn create(owner: *std.Build, name: []const u8) *Run { |
| 227 | 202 | .stdio_limit = .unlimited, |
| 228 | 203 | .captured_stdout = null, |
| 229 | 204 | .captured_stderr = null, |
| 230 | .dep_output_file = null, | |
| 231 | 205 | .has_side_effects = false, |
| 232 | .fuzz_tests = .empty, | |
| 233 | .rebuilt_executable = null, | |
| 234 | 206 | .producer = null, |
| 235 | 207 | }; |
| 236 | 208 | return run; |
| ... | ... | @@ -242,13 +214,9 @@ pub fn setName(run: *Run, name: []const u8) void { |
| 242 | 214 | } |
| 243 | 215 | |
| 244 | 216 | pub fn enableTestRunnerMode(run: *Run) void { |
| 245 | const b = run.step.owner; | |
| 217 | if (run.test_runner_mode) return; | |
| 246 | 218 | run.stdio = .zig_test; |
| 247 | run.addPrefixedDirectoryArg("--cache-dir=", .{ .cwd_relative = b.cache_root.path orelse "." }); | |
| 248 | run.addArgs(&.{ | |
| 249 | b.fmt("--seed=0x{x}", .{b.graph.random_seed}), | |
| 250 | "--listen=-", | |
| 251 | }); | |
| 219 | run.test_runner_mode = true; | |
| 252 | 220 | } |
| 253 | 221 | |
| 254 | 222 | pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void { |
| ... | ... | @@ -256,13 +224,14 @@ pub fn addArtifactArg(run: *Run, artifact: *Step.Compile) void { |
| 256 | 224 | } |
| 257 | 225 | |
| 258 | 226 | pub fn addPrefixedArtifactArg(run: *Run, prefix: []const u8, artifact: *Step.Compile) void { |
| 259 | const b = run.step.owner; | |
| 227 | const graph = run.step.owner.graph; | |
| 228 | const arena = graph.arena; | |
| 260 | 229 | |
| 261 | 230 | const prefixed_artifact: PrefixedArtifact = .{ |
| 262 | .prefix = b.dupe(prefix), | |
| 231 | .prefix = graph.dupeString(prefix), | |
| 263 | 232 | .artifact = artifact, |
| 264 | 233 | }; |
| 265 | run.argv.append(b.allocator, .{ .artifact = prefixed_artifact }) catch @panic("OOM"); | |
| 234 | run.argv.append(arena, .{ .artifact = prefixed_artifact }) catch @panic("OOM"); | |
| 266 | 235 | |
| 267 | 236 | const bin_file = artifact.getEmittedBin(); |
| 268 | 237 | bin_file.addStepDependencies(&run.step); |
| ... | ... | @@ -273,21 +242,23 @@ pub fn addPrefixedArtifactArg(run: *Run, prefix: []const u8, artifact: *Step.Com |
| 273 | 242 | /// Returns a `std.Build.LazyPath` which can be used as inputs to other APIs |
| 274 | 243 | /// throughout the build system. |
| 275 | 244 | /// |
| 245 | /// `sub_path` is the name of the generated output file which may have zero or | |
| 246 | /// more path components. | |
| 247 | /// | |
| 276 | 248 | /// Related: |
| 277 | 249 | /// * `addPrefixedOutputFileArg` - same thing but prepends a string to the argument |
| 278 | 250 | /// * `addFileArg` - for input files given to the child process |
| 279 | pub fn addOutputFileArg(run: *Run, basename: []const u8) std.Build.LazyPath { | |
| 280 | return run.addPrefixedOutputFileArg("", basename); | |
| 251 | pub fn addOutputFileArg(run: *Run, sub_path: []const u8) std.Build.LazyPath { | |
| 252 | return run.addPrefixedOutputFileArg("", sub_path); | |
| 281 | 253 | } |
| 282 | 254 | |
| 283 | 255 | /// Provides a file path as a command line argument to the command being run. |
| 284 | /// Asserts `basename` is not empty. | |
| 285 | 256 | /// |
| 286 | /// For example, a prefix of "-o" and basename of "output.txt" will result in | |
| 257 | /// For example, a prefix of "-o" and `sub_path` of "output.txt" will result in | |
| 287 | 258 | /// the child process seeing something like this: "-ozig-cache/.../output.txt" |
| 288 | 259 | /// |
| 289 | 260 | /// The child process will see a single argument, regardless of whether the |
| 290 | /// prefix or basename have spaces. | |
| 261 | /// prefix or `sub_path` have spaces. | |
| 291 | 262 | /// |
| 292 | 263 | /// The returned `std.Build.LazyPath` can be used as inputs to other APIs |
| 293 | 264 | /// throughout the build system. |
| ... | ... | @@ -298,24 +269,30 @@ pub fn addOutputFileArg(run: *Run, basename: []const u8) std.Build.LazyPath { |
| 298 | 269 | pub fn addPrefixedOutputFileArg( |
| 299 | 270 | run: *Run, |
| 300 | 271 | prefix: []const u8, |
| 301 | basename: []const u8, | |
| 272 | /// The name of the generated output file which may have zero or more path | |
| 273 | /// components. | |
| 274 | /// | |
| 275 | /// Asserted to be non-empty. | |
| 276 | sub_path: []const u8, | |
| 302 | 277 | ) std.Build.LazyPath { |
| 303 | 278 | const b = run.step.owner; |
| 304 | if (basename.len == 0) @panic("basename must not be empty"); | |
| 279 | const graph = b.graph; | |
| 280 | const arena = graph.arena; | |
| 281 | assert(sub_path.len != 0); | |
| 305 | 282 | |
| 306 | const output = b.allocator.create(Output) catch @panic("OOM"); | |
| 283 | const output = graph.create(Output); | |
| 307 | 284 | output.* = .{ |
| 308 | .prefix = b.dupe(prefix), | |
| 309 | .basename = b.dupe(basename), | |
| 310 | .generated_file = .{ .step = &run.step }, | |
| 285 | .prefix = graph.dupeString(prefix), | |
| 286 | .basename = graph.dupeString(sub_path), | |
| 287 | .generated_file = graph.addGeneratedFile(&run.step), | |
| 311 | 288 | }; |
| 312 | run.argv.append(b.allocator, .{ .output_file = output }) catch @panic("OOM"); | |
| 289 | run.argv.append(arena, .{ .output_file = output }) catch @panic("OOM"); | |
| 313 | 290 | |
| 314 | 291 | if (run.rename_step_with_output_arg) { |
| 315 | run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename })); | |
| 292 | run.setName(b.fmt("{s} ({s})", .{ run.step.name, sub_path })); | |
| 316 | 293 | } |
| 317 | 294 | |
| 318 | return .{ .generated = .{ .file = &output.generated_file } }; | |
| 295 | return .{ .generated = .{ .index = output.generated_file } }; | |
| 319 | 296 | } |
| 320 | 297 | |
| 321 | 298 | /// Appends an input file to the command line arguments. |
| ... | ... | @@ -344,13 +321,14 @@ pub fn addFileArg(run: *Run, lp: std.Build.LazyPath) void { |
| 344 | 321 | /// * `addFileArg` - same thing but without the prefix |
| 345 | 322 | /// * `addOutputFileArg` - for files generated by the child process |
| 346 | 323 | pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void { |
| 347 | const b = run.step.owner; | |
| 324 | const graph = run.step.owner.graph; | |
| 325 | const arena = graph.arena; | |
| 348 | 326 | |
| 349 | 327 | const prefixed_file_source: PrefixedLazyPath = .{ |
| 350 | .prefix = b.dupe(prefix), | |
| 351 | .lazy_path = lp.dupe(b), | |
| 328 | .prefix = graph.dupeString(prefix), | |
| 329 | .lazy_path = lp.dupe(graph), | |
| 352 | 330 | }; |
| 353 | run.argv.append(b.allocator, .{ .lazy_path = prefixed_file_source }) catch @panic("OOM"); | |
| 331 | run.argv.append(arena, .{ .lazy_path = prefixed_file_source }) catch @panic("OOM"); | |
| 354 | 332 | lp.addStepDependencies(&run.step); |
| 355 | 333 | } |
| 356 | 334 | |
| ... | ... | @@ -391,7 +369,8 @@ pub fn addFileContentArg(run: *Run, lp: std.Build.LazyPath) void { |
| 391 | 369 | /// Related: |
| 392 | 370 | /// * `addFileContentArg` - same thing but without the prefix |
| 393 | 371 | pub fn addPrefixedFileContentArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void { |
| 394 | const b = run.step.owner; | |
| 372 | const graph = run.step.owner.graph; | |
| 373 | const arena = graph.arena; | |
| 395 | 374 | |
| 396 | 375 | // Some parts of this step's configure phase API rely on the first argument being somewhat |
| 397 | 376 | // transparent/readable, but the content of the file specified by `lp` remains completely |
| ... | ... | @@ -401,10 +380,10 @@ pub fn addPrefixedFileContentArg(run: *Run, prefix: []const u8, lp: std.Build.La |
| 401 | 380 | } |
| 402 | 381 | |
| 403 | 382 | const prefixed_file_source: PrefixedLazyPath = .{ |
| 404 | .prefix = b.dupe(prefix), | |
| 405 | .lazy_path = lp.dupe(b), | |
| 383 | .prefix = graph.dupeString(prefix), | |
| 384 | .lazy_path = lp.dupe(graph), | |
| 406 | 385 | }; |
| 407 | run.argv.append(b.allocator, .{ .file_content = prefixed_file_source }) catch @panic("OOM"); | |
| 386 | run.argv.append(arena, .{ .file_content = prefixed_file_source }) catch @panic("OOM"); | |
| 408 | 387 | lp.addStepDependencies(&run.step); |
| 409 | 388 | } |
| 410 | 389 | |
| ... | ... | @@ -441,21 +420,22 @@ pub fn addPrefixedOutputDirectoryArg( |
| 441 | 420 | basename: []const u8, |
| 442 | 421 | ) std.Build.LazyPath { |
| 443 | 422 | if (basename.len == 0) @panic("basename must not be empty"); |
| 444 | const b = run.step.owner; | |
| 423 | const graph = run.step.owner.graph; | |
| 424 | const arena = graph.arena; | |
| 445 | 425 | |
| 446 | const output = b.allocator.create(Output) catch @panic("OOM"); | |
| 426 | const output = arena.create(Output) catch @panic("OOM"); | |
| 447 | 427 | output.* = .{ |
| 448 | .prefix = b.dupe(prefix), | |
| 449 | .basename = b.dupe(basename), | |
| 450 | .generated_file = .{ .step = &run.step }, | |
| 428 | .prefix = graph.dupeString(prefix), | |
| 429 | .basename = graph.dupeString(basename), | |
| 430 | .generated_file = graph.addGeneratedFile(&run.step), | |
| 451 | 431 | }; |
| 452 | run.argv.append(b.allocator, .{ .output_directory = output }) catch @panic("OOM"); | |
| 432 | run.argv.append(arena, .{ .output_directory = output }) catch @panic("OOM"); | |
| 453 | 433 | |
| 454 | 434 | if (run.rename_step_with_output_arg) { |
| 455 | run.setName(b.fmt("{s} ({s})", .{ run.step.name, basename })); | |
| 435 | run.setName(std.fmt.allocPrint(arena, "{s} ({s})", .{ run.step.name, basename }) catch @panic("OOM")); | |
| 456 | 436 | } |
| 457 | 437 | |
| 458 | return .{ .generated = .{ .file = &output.generated_file } }; | |
| 438 | return .{ .generated = .{ .index = output.generated_file } }; | |
| 459 | 439 | } |
| 460 | 440 | |
| 461 | 441 | pub fn addDirectoryArg(run: *Run, lazy_directory: std.Build.LazyPath) void { |
| ... | ... | @@ -463,10 +443,11 @@ pub fn addDirectoryArg(run: *Run, lazy_directory: std.Build.LazyPath) void { |
| 463 | 443 | } |
| 464 | 444 | |
| 465 | 445 | pub fn addPrefixedDirectoryArg(run: *Run, prefix: []const u8, lazy_directory: std.Build.LazyPath) void { |
| 466 | const b = run.step.owner; | |
| 467 | run.argv.append(b.allocator, .{ .decorated_directory = .{ | |
| 468 | .prefix = b.dupe(prefix), | |
| 469 | .lazy_path = lazy_directory.dupe(b), | |
| 446 | const graph = run.step.owner.graph; | |
| 447 | const arena = graph.arena; | |
| 448 | run.argv.append(arena, .{ .decorated_directory = .{ | |
| 449 | .prefix = graph.dupeString(prefix), | |
| 450 | .lazy_path = lazy_directory.dupe(graph), | |
| 470 | 451 | .suffix = "", |
| 471 | 452 | } }) catch @panic("OOM"); |
| 472 | 453 | lazy_directory.addStepDependencies(&run.step); |
| ... | ... | @@ -478,11 +459,12 @@ pub fn addDecoratedDirectoryArg( |
| 478 | 459 | lazy_directory: std.Build.LazyPath, |
| 479 | 460 | suffix: []const u8, |
| 480 | 461 | ) void { |
| 481 | const b = run.step.owner; | |
| 482 | run.argv.append(b.allocator, .{ .decorated_directory = .{ | |
| 483 | .prefix = b.dupe(prefix), | |
| 484 | .lazy_path = lazy_directory.dupe(b), | |
| 485 | .suffix = b.dupe(suffix), | |
| 462 | const graph = run.step.owner.graph; | |
| 463 | const arena = graph.arena; | |
| 464 | run.argv.append(arena, .{ .decorated_directory = .{ | |
| 465 | .prefix = graph.dupeString(prefix), | |
| 466 | .lazy_path = lazy_directory.dupe(graph), | |
| 467 | .suffix = graph.dupeString(suffix), | |
| 486 | 468 | } }) catch @panic("OOM"); |
| 487 | 469 | lazy_directory.addStepDependencies(&run.step); |
| 488 | 470 | } |
| ... | ... | @@ -496,34 +478,63 @@ pub fn addDepFileOutputArg(run: *Run, basename: []const u8) std.Build.LazyPath { |
| 496 | 478 | |
| 497 | 479 | /// Add a prefixed path argument to a dep file (.d) for the child process to |
| 498 | 480 | /// write its discovered additional dependencies. |
| 499 | /// Only one dep file argument is allowed by instance. | |
| 500 | 481 | pub fn addPrefixedDepFileOutputArg(run: *Run, prefix: []const u8, basename: []const u8) std.Build.LazyPath { |
| 501 | 482 | const b = run.step.owner; |
| 502 | assert(run.dep_output_file == null); | |
| 483 | const graph = b.graph; | |
| 484 | const arena = graph.arena; | |
| 503 | 485 | |
| 504 | const dep_file = b.allocator.create(Output) catch @panic("OOM"); | |
| 486 | const dep_file = arena.create(Output) catch @panic("OOM"); | |
| 505 | 487 | dep_file.* = .{ |
| 506 | .prefix = b.dupe(prefix), | |
| 507 | .basename = b.dupe(basename), | |
| 508 | .generated_file = .{ .step = &run.step }, | |
| 488 | .prefix = graph.dupeString(prefix), | |
| 489 | .basename = graph.dupeString(basename), | |
| 490 | .generated_file = graph.addGeneratedFile(&run.step), | |
| 509 | 491 | }; |
| 510 | 492 | |
| 511 | run.dep_output_file = dep_file; | |
| 493 | run.argv.append(arena, .{ .output_file_dep = dep_file }) catch @panic("OOM"); | |
| 512 | 494 | |
| 513 | run.argv.append(b.allocator, .{ .output_file = dep_file }) catch @panic("OOM"); | |
| 514 | ||
| 515 | return .{ .generated = .{ .file = &dep_file.generated_file } }; | |
| 495 | return .{ .generated = .{ .index = dep_file.generated_file } }; | |
| 516 | 496 | } |
| 517 | 497 | |
| 498 | /// Appends the contents of `arg`, verbatim, to the command line that will be | |
| 499 | /// passed to the process being run. | |
| 500 | /// | |
| 501 | /// If `arg` is an input file, `addFileInput` (or related function) must be | |
| 502 | /// used instead to ensure correct cache behavior. | |
| 503 | /// | |
| 504 | /// If `arg` is an output file, `addOutputFileArg` (or related function) must | |
| 505 | /// be used instead to ensure correct cache behavior. | |
| 518 | 506 | pub fn addArg(run: *Run, arg: []const u8) void { |
| 519 | const b = run.step.owner; | |
| 520 | run.argv.append(b.allocator, .{ .bytes = b.dupe(arg) }) catch @panic("OOM"); | |
| 507 | const graph = run.step.owner.graph; | |
| 508 | const arena = graph.arena; | |
| 509 | run.argv.append(arena, .{ .bytes = graph.dupeString(arg) }) catch @panic("OOM"); | |
| 521 | 510 | } |
| 522 | 511 | |
| 512 | /// Appends each of `args`, verbatim, to the command line that will be passed | |
| 513 | /// to the process being run. | |
| 514 | /// | |
| 515 | /// If any element of `args` is an input file, `addFileInput` must be used | |
| 516 | /// instead to ensure correct cache behavior. | |
| 517 | /// | |
| 518 | /// If any element of `args` is an output file, `addOutputFileArg` (or related | |
| 519 | /// function) must be used instead to ensure correct cache behavior. | |
| 523 | 520 | pub fn addArgs(run: *Run, args: []const []const u8) void { |
| 524 | 521 | for (args) |arg| run.addArg(arg); |
| 525 | 522 | } |
| 526 | 523 | |
| 524 | /// Appends the extra arguments provided to `zig build` to the command line | |
| 525 | /// that will be passed to the process being run. | |
| 526 | /// | |
| 527 | /// This causes the step to be considered to have side effects, disabling | |
| 528 | /// caching. | |
| 529 | /// | |
| 530 | /// In the example command `zig build run -- arg1 arg2`, "arg1" and "arg2" will | |
| 531 | /// be passed to the process being run. | |
| 532 | pub fn addPassthruArgs(run: *Run) void { | |
| 533 | const graph = run.step.owner.graph; | |
| 534 | const arena = graph.arena; | |
| 535 | run.argv.append(arena, .passthru) catch @panic("OOM"); | |
| 536 | } | |
| 537 | ||
| 527 | 538 | pub fn setStdIn(run: *Run, stdin: StdIn) void { |
| 528 | 539 | switch (stdin) { |
| 529 | 540 | .lazy_path => |lazy_path| lazy_path.addStepDependencies(&run.step), |
| ... | ... | @@ -533,8 +544,9 @@ pub fn setStdIn(run: *Run, stdin: StdIn) void { |
| 533 | 544 | } |
| 534 | 545 | |
| 535 | 546 | pub fn setCwd(run: *Run, cwd: Build.LazyPath) void { |
| 547 | const graph = run.step.owner.graph; | |
| 536 | 548 | cwd.addStepDependencies(&run.step); |
| 537 | run.cwd = cwd.dupe(run.step.owner); | |
| 549 | run.cwd = cwd.dupe(graph); | |
| 538 | 550 | } |
| 539 | 551 | |
| 540 | 552 | pub fn clearEnvironment(run: *Run) void { |
| ... | ... | @@ -560,7 +572,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void { |
| 560 | 572 | .decorated_directory => false, |
| 561 | 573 | .file_content => unreachable, // not allowed as first arg |
| 562 | 574 | .bytes => |bytes| std.mem.endsWith(u8, bytes, ".exe"), |
| 563 | .output_file, .output_directory => false, | |
| 575 | .output_file, .output_file_dep, .output_directory => false, | |
| 564 | 576 | }; |
| 565 | 577 | const key = if (use_wine) "WINEPATH" else "PATH"; |
| 566 | 578 | const prev_path = environ_map.get(key); |
| ... | ... | @@ -604,24 +616,28 @@ pub fn removeEnvironmentVariable(run: *Run, key: []const u8) void { |
| 604 | 616 | |
| 605 | 617 | /// Adds a check for exact stderr match. Does not add any other checks. |
| 606 | 618 | pub fn expectStdErrEqual(run: *Run, bytes: []const u8) void { |
| 607 | run.addCheck(.{ .expect_stderr_exact = run.step.owner.dupe(bytes) }); | |
| 619 | const graph = run.step.owner.graph; | |
| 620 | run.addCheck(.{ .expect_stderr_exact = graph.dupeString(bytes) }); | |
| 608 | 621 | } |
| 609 | 622 | |
| 610 | 623 | pub fn expectStdErrMatch(run: *Run, bytes: []const u8) void { |
| 611 | run.addCheck(.{ .expect_stderr_match = run.step.owner.dupe(bytes) }); | |
| 624 | const graph = run.step.owner.graph; | |
| 625 | run.addCheck(.{ .expect_stderr_match = graph.dupeString(bytes) }); | |
| 612 | 626 | } |
| 613 | 627 | |
| 614 | 628 | /// Adds a check for exact stdout match as well as a check for exit code 0, if |
| 615 | 629 | /// there is not already an expected termination check. |
| 616 | 630 | pub fn expectStdOutEqual(run: *Run, bytes: []const u8) void { |
| 617 | run.addCheck(.{ .expect_stdout_exact = run.step.owner.dupe(bytes) }); | |
| 631 | const graph = run.step.owner.graph; | |
| 632 | run.addCheck(.{ .expect_stdout_exact = graph.dupeString(bytes) }); | |
| 618 | 633 | if (!run.hasTermCheck()) run.expectExitCode(0); |
| 619 | 634 | } |
| 620 | 635 | |
| 621 | 636 | /// Adds a check for stdout match as well as a check for exit code 0, if there |
| 622 | 637 | /// is not already an expected termination check. |
| 623 | 638 | pub fn expectStdOutMatch(run: *Run, bytes: []const u8) void { |
| 624 | run.addCheck(.{ .expect_stdout_match = run.step.owner.dupe(bytes) }); | |
| 639 | const graph = run.step.owner.graph; | |
| 640 | run.addCheck(.{ .expect_stdout_match = graph.dupeString(bytes) }); | |
| 625 | 641 | if (!run.hasTermCheck()) run.expectExitCode(0); |
| 626 | 642 | } |
| 627 | 643 | |
| ... | ... | @@ -656,20 +672,22 @@ pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa |
| 656 | 672 | assert(run.stdio != .zig_test); |
| 657 | 673 | |
| 658 | 674 | const b = run.step.owner; |
| 675 | const graph = b.graph; | |
| 676 | const arena = graph.arena; | |
| 659 | 677 | |
| 660 | if (run.captured_stderr) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } }; | |
| 678 | if (run.captured_stderr) |captured| return .{ .generated = .{ .index = captured.output.generated_file } }; | |
| 661 | 679 | |
| 662 | const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM"); | |
| 680 | const captured = arena.create(CapturedStdIo) catch @panic("OOM"); | |
| 663 | 681 | captured.* = .{ |
| 664 | 682 | .output = .{ |
| 665 | 683 | .prefix = "", |
| 666 | .basename = if (options.basename) |basename| b.dupe(basename) else "stderr", | |
| 667 | .generated_file = .{ .step = &run.step }, | |
| 684 | .basename = if (options.basename) |basename| graph.dupeString(basename) else "stderr", | |
| 685 | .generated_file = graph.addGeneratedFile(&run.step), | |
| 668 | 686 | }, |
| 669 | 687 | .trim_whitespace = options.trim_whitespace, |
| 670 | 688 | }; |
| 671 | 689 | run.captured_stderr = captured; |
| 672 | return .{ .generated = .{ .file = &captured.output.generated_file } }; | |
| 690 | return .{ .generated = .{ .index = captured.output.generated_file } }; | |
| 673 | 691 | } |
| 674 | 692 | |
| 675 | 693 | pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { |
| ... | ... | @@ -677,20 +695,22 @@ pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa |
| 677 | 695 | assert(run.stdio != .zig_test); |
| 678 | 696 | |
| 679 | 697 | const b = run.step.owner; |
| 698 | const graph = b.graph; | |
| 699 | const arena = graph.arena; | |
| 680 | 700 | |
| 681 | if (run.captured_stdout) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } }; | |
| 701 | if (run.captured_stdout) |captured| return .{ .generated = .{ .index = captured.output.generated_file } }; | |
| 682 | 702 | |
| 683 | const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM"); | |
| 703 | const captured = arena.create(CapturedStdIo) catch @panic("OOM"); | |
| 684 | 704 | captured.* = .{ |
| 685 | 705 | .output = .{ |
| 686 | 706 | .prefix = "", |
| 687 | .basename = if (options.basename) |basename| b.dupe(basename) else "stdout", | |
| 688 | .generated_file = .{ .step = &run.step }, | |
| 707 | .basename = if (options.basename) |basename| graph.dupeString(basename) else "stdout", | |
| 708 | .generated_file = graph.addGeneratedFile(&run.step), | |
| 689 | 709 | }, |
| 690 | 710 | .trim_whitespace = options.trim_whitespace, |
| 691 | 711 | }; |
| 692 | 712 | run.captured_stdout = captured; |
| 693 | return .{ .generated = .{ .file = &captured.output.generated_file } }; | |
| 713 | return .{ .generated = .{ .index = captured.output.generated_file } }; | |
| 694 | 714 | } |
| 695 | 715 | |
| 696 | 716 | /// Adds an additional input files that, when modified, indicates that this Run |
| ... | ... | @@ -698,2111 +718,9 @@ pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPa |
| 698 | 718 | /// If the Run step is determined to have side-effects, the Run step is always |
| 699 | 719 | /// executed when it appears in the build graph, regardless of whether this |
| 700 | 720 | /// file has been modified. |
| 701 | pub fn addFileInput(self: *Run, file_input: std.Build.LazyPath) void { | |
| 702 | file_input.addStepDependencies(&self.step); | |
| 703 | self.file_inputs.append(self.step.owner.allocator, file_input.dupe(self.step.owner)) catch @panic("OOM"); | |
| 704 | } | |
| 705 | ||
| 706 | /// Returns whether the Run step has side effects *other than* updating the output arguments. | |
| 707 | fn hasSideEffects(run: Run) bool { | |
| 708 | if (run.has_side_effects) return true; | |
| 709 | return switch (run.stdio) { | |
| 710 | .infer_from_args => !run.hasAnyOutputArgs(), | |
| 711 | .inherit => true, | |
| 712 | .check => false, | |
| 713 | .zig_test => false, | |
| 714 | }; | |
| 715 | } | |
| 716 | ||
| 717 | fn hasAnyOutputArgs(run: Run) bool { | |
| 718 | if (run.captured_stdout != null) return true; | |
| 719 | if (run.captured_stderr != null) return true; | |
| 720 | for (run.argv.items) |arg| switch (arg) { | |
| 721 | .output_file, .output_directory => return true, | |
| 722 | else => continue, | |
| 723 | }; | |
| 724 | return false; | |
| 725 | } | |
| 726 | ||
| 727 | fn checksContainStdout(checks: []const StdIo.Check) bool { | |
| 728 | for (checks) |check| switch (check) { | |
| 729 | .expect_stderr_exact, | |
| 730 | .expect_stderr_match, | |
| 731 | .expect_term, | |
| 732 | => continue, | |
| 733 | ||
| 734 | .expect_stdout_exact, | |
| 735 | .expect_stdout_match, | |
| 736 | => return true, | |
| 737 | }; | |
| 738 | return false; | |
| 739 | } | |
| 740 | ||
| 741 | fn checksContainStderr(checks: []const StdIo.Check) bool { | |
| 742 | for (checks) |check| switch (check) { | |
| 743 | .expect_stdout_exact, | |
| 744 | .expect_stdout_match, | |
| 745 | .expect_term, | |
| 746 | => continue, | |
| 747 | ||
| 748 | .expect_stderr_exact, | |
| 749 | .expect_stderr_match, | |
| 750 | => return true, | |
| 751 | }; | |
| 752 | return false; | |
| 753 | } | |
| 754 | ||
| 755 | /// If `path` is cwd-relative, make it relative to the cwd of the child instead. | |
| 756 | /// | |
| 757 | /// Whenever a path is included in the argv of a child, it should be put through this function first | |
| 758 | /// to make sure the child doesn't see paths relative to a cwd other than its own. | |
| 759 | fn convertPathArg(run: *Run, path: Build.Cache.Path) []const u8 { | |
| 760 | const b = run.step.owner; | |
| 761 | const graph = b.graph; | |
| 721 | pub fn addFileInput(run: *Run, file_input: std.Build.LazyPath) void { | |
| 722 | const graph = run.step.owner.graph; | |
| 762 | 723 | const arena = graph.arena; |
| 763 | ||
| 764 | const path_str = path.toString(arena) catch @panic("OOM"); | |
| 765 | if (Dir.path.isAbsolute(path_str)) { | |
| 766 | // Absolute paths don't need changing. | |
| 767 | return path_str; | |
| 768 | } | |
| 769 | const child_cwd_rel: []const u8 = rel: { | |
| 770 | const child_lazy_cwd = run.cwd orelse break :rel path_str; | |
| 771 | const child_cwd = child_lazy_cwd.getPath3(b, &run.step).toString(arena) catch @panic("OOM"); | |
| 772 | // Convert it from relative to *our* cwd, to relative to the *child's* cwd. | |
| 773 | break :rel Dir.path.relative(arena, graph.cache.cwd, &graph.environ_map, child_cwd, path_str) catch @panic("OOM"); | |
| 774 | }; | |
| 775 | // Not every path can be made relative, e.g. if the path and the child cwd are on different | |
| 776 | // disk designators on Windows. In that case, `relative` will return an absolute path which we can | |
| 777 | // just return. | |
| 778 | if (Dir.path.isAbsolute(child_cwd_rel)) return child_cwd_rel; | |
| 779 | ||
| 780 | // We're not done yet. In some cases this path must be prefixed with './': | |
| 781 | // * On POSIX, the executable name cannot be a single component like 'foo' | |
| 782 | // * Some executables might treat a leading '-' like a flag, which we must avoid | |
| 783 | // There's no harm in it, so just *always* apply this prefix. | |
| 784 | return Dir.path.join(arena, &.{ ".", child_cwd_rel }) catch @panic("OOM"); | |
| 785 | } | |
| 786 | ||
| 787 | const IndexedOutput = struct { | |
| 788 | index: usize, | |
| 789 | tag: @typeInfo(Arg).@"union".tag_type.?, | |
| 790 | output: *Output, | |
| 791 | }; | |
| 792 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 793 | const b = step.owner; | |
| 794 | const io = b.graph.io; | |
| 795 | const arena = b.allocator; | |
| 796 | const run: *Run = @fieldParentPtr("step", step); | |
| 797 | const has_side_effects = run.hasSideEffects(); | |
| 798 | ||
| 799 | var argv_list = std.array_list.Managed([]const u8).init(arena); | |
| 800 | var output_placeholders = std.array_list.Managed(IndexedOutput).init(arena); | |
| 801 | ||
| 802 | var man = b.graph.cache.obtain(); | |
| 803 | defer man.deinit(); | |
| 804 | ||
| 805 | if (run.environ_map) |environ_map| { | |
| 806 | for (environ_map.keys(), environ_map.values()) |key, value| { | |
| 807 | man.hash.addBytes(key); | |
| 808 | man.hash.addBytes(value); | |
| 809 | } | |
| 810 | } | |
| 811 | ||
| 812 | man.hash.add(run.color); | |
| 813 | man.hash.add(run.disable_zig_progress); | |
| 814 | ||
| 815 | for (run.argv.items) |arg| { | |
| 816 | switch (arg) { | |
| 817 | .bytes => |bytes| { | |
| 818 | try argv_list.append(bytes); | |
| 819 | man.hash.addBytes(bytes); | |
| 820 | }, | |
| 821 | .lazy_path => |file| { | |
| 822 | const file_path = file.lazy_path.getPath3(b, step); | |
| 823 | try argv_list.append(b.fmt("{s}{s}", .{ file.prefix, run.convertPathArg(file_path) })); | |
| 824 | man.hash.addBytes(file.prefix); | |
| 825 | _ = try man.addFilePath(file_path, null); | |
| 826 | }, | |
| 827 | .decorated_directory => |dd| { | |
| 828 | const file_path = dd.lazy_path.getPath3(b, step); | |
| 829 | const resolved_arg = b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix }); | |
| 830 | try argv_list.append(resolved_arg); | |
| 831 | man.hash.addBytes(resolved_arg); | |
| 832 | }, | |
| 833 | .file_content => |file_plp| { | |
| 834 | const file_path = file_plp.lazy_path.getPath3(b, step); | |
| 835 | ||
| 836 | var result: std.Io.Writer.Allocating = .init(arena); | |
| 837 | errdefer result.deinit(); | |
| 838 | result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; | |
| 839 | ||
| 840 | const file = file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}) catch |err| { | |
| 841 | return step.fail( | |
| 842 | "unable to open input file '{f}': {t}", | |
| 843 | .{ file_path, err }, | |
| 844 | ); | |
| 845 | }; | |
| 846 | defer file.close(io); | |
| 847 | ||
| 848 | var buf: [1024]u8 = undefined; | |
| 849 | var file_reader = file.reader(io, &buf); | |
| 850 | _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { | |
| 851 | error.ReadFailed => return step.fail( | |
| 852 | "failed to read from '{f}': {t}", | |
| 853 | .{ file_path, file_reader.err.? }, | |
| 854 | ), | |
| 855 | error.WriteFailed => return error.OutOfMemory, | |
| 856 | }; | |
| 857 | ||
| 858 | try argv_list.append(result.written()); | |
| 859 | man.hash.addBytes(file_plp.prefix); | |
| 860 | _ = try man.addFilePath(file_path, null); | |
| 861 | }, | |
| 862 | .artifact => |pa| { | |
| 863 | const artifact = pa.artifact; | |
| 864 | ||
| 865 | if (artifact.rootModuleTarget().os.tag == .windows) { | |
| 866 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH | |
| 867 | run.addPathForDynLibs(artifact); | |
| 868 | } | |
| 869 | const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?; | |
| 870 | ||
| 871 | try argv_list.append(b.fmt("{s}{s}", .{ | |
| 872 | pa.prefix, | |
| 873 | run.convertPathArg(.{ .root_dir = .cwd(), .sub_path = file_path }), | |
| 874 | })); | |
| 875 | ||
| 876 | _ = try man.addFile(file_path, null); | |
| 877 | }, | |
| 878 | .output_file, .output_directory => |output| { | |
| 879 | man.hash.addBytes(output.prefix); | |
| 880 | man.hash.addBytes(output.basename); | |
| 881 | // Add a placeholder into the argument list because we need the | |
| 882 | // manifest hash to be updated with all arguments before the | |
| 883 | // object directory is computed. | |
| 884 | try output_placeholders.append(.{ | |
| 885 | .index = argv_list.items.len, | |
| 886 | .tag = arg, | |
| 887 | .output = output, | |
| 888 | }); | |
| 889 | _ = try argv_list.addOne(); | |
| 890 | }, | |
| 891 | } | |
| 892 | } | |
| 893 | ||
| 894 | switch (run.stdin) { | |
| 895 | .bytes => |bytes| { | |
| 896 | man.hash.addBytes(bytes); | |
| 897 | }, | |
| 898 | .lazy_path => |lazy_path| { | |
| 899 | const file_path = lazy_path.getPath2(b, step); | |
| 900 | _ = try man.addFile(file_path, null); | |
| 901 | }, | |
| 902 | .none => {}, | |
| 903 | } | |
| 904 | ||
| 905 | if (run.captured_stdout) |captured| { | |
| 906 | man.hash.addBytes(captured.output.basename); | |
| 907 | man.hash.add(captured.trim_whitespace); | |
| 908 | } | |
| 909 | ||
| 910 | if (run.captured_stderr) |captured| { | |
| 911 | man.hash.addBytes(captured.output.basename); | |
| 912 | man.hash.add(captured.trim_whitespace); | |
| 913 | } | |
| 914 | ||
| 915 | hashStdIo(&man.hash, run.stdio); | |
| 916 | ||
| 917 | for (run.file_inputs.items) |lazy_path| { | |
| 918 | _ = try man.addFile(lazy_path.getPath2(b, step), null); | |
| 919 | } | |
| 920 | ||
| 921 | if (run.cwd) |cwd| { | |
| 922 | const cwd_path = cwd.getPath3(b, step); | |
| 923 | _ = man.hash.addBytes(try cwd_path.toString(arena)); | |
| 924 | } | |
| 925 | ||
| 926 | if (!has_side_effects and try step.cacheHitAndWatch(&man)) { | |
| 927 | // cache hit, skip running command | |
| 928 | const digest = man.final(); | |
| 929 | ||
| 930 | try populateGeneratedPaths( | |
| 931 | arena, | |
| 932 | output_placeholders.items, | |
| 933 | run.captured_stdout, | |
| 934 | run.captured_stderr, | |
| 935 | b.cache_root, | |
| 936 | &digest, | |
| 937 | ); | |
| 938 | ||
| 939 | step.result_cached = true; | |
| 940 | return; | |
| 941 | } | |
| 942 | ||
| 943 | const dep_output_file = run.dep_output_file orelse { | |
| 944 | // We already know the final output paths, use them directly. | |
| 945 | const digest = if (has_side_effects) | |
| 946 | man.hash.final() | |
| 947 | else | |
| 948 | man.final(); | |
| 949 | ||
| 950 | try populateGeneratedPaths( | |
| 951 | arena, | |
| 952 | output_placeholders.items, | |
| 953 | run.captured_stdout, | |
| 954 | run.captured_stderr, | |
| 955 | b.cache_root, | |
| 956 | &digest, | |
| 957 | ); | |
| 958 | ||
| 959 | const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; | |
| 960 | for (output_placeholders.items) |placeholder| { | |
| 961 | const output_sub_path = b.pathJoin(&.{ output_dir_path, placeholder.output.basename }); | |
| 962 | const output_sub_dir_path = switch (placeholder.tag) { | |
| 963 | .output_file => Dir.path.dirname(output_sub_path).?, | |
| 964 | .output_directory => output_sub_path, | |
| 965 | else => unreachable, | |
| 966 | }; | |
| 967 | b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| { | |
| 968 | return step.fail("unable to make path '{f}{s}': {s}", .{ | |
| 969 | b.cache_root, output_sub_dir_path, @errorName(err), | |
| 970 | }); | |
| 971 | }; | |
| 972 | const arg_output_path = run.convertPathArg(.{ | |
| 973 | .root_dir = .cwd(), | |
| 974 | .sub_path = placeholder.output.generated_file.getPath(), | |
| 975 | }); | |
| 976 | argv_list.items[placeholder.index] = if (placeholder.output.prefix.len == 0) | |
| 977 | arg_output_path | |
| 978 | else | |
| 979 | b.fmt("{s}{s}", .{ placeholder.output.prefix, arg_output_path }); | |
| 980 | } | |
| 981 | ||
| 982 | try runCommand(run, argv_list.items, has_side_effects, output_dir_path, options, null); | |
| 983 | if (!has_side_effects) try step.writeManifestAndWatch(&man); | |
| 984 | return; | |
| 985 | }; | |
| 986 | ||
| 987 | // We do not know the final output paths yet, use temp paths to run the command. | |
| 988 | var rand_int: u64 = undefined; | |
| 989 | io.random(@ptrCast(&rand_int)); | |
| 990 | const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); | |
| 991 | ||
| 992 | for (output_placeholders.items) |placeholder| { | |
| 993 | const output_components = .{ tmp_dir_path, placeholder.output.basename }; | |
| 994 | const output_sub_path = b.pathJoin(&output_components); | |
| 995 | const output_sub_dir_path = switch (placeholder.tag) { | |
| 996 | .output_file => Dir.path.dirname(output_sub_path).?, | |
| 997 | .output_directory => output_sub_path, | |
| 998 | else => unreachable, | |
| 999 | }; | |
| 1000 | b.cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| { | |
| 1001 | return step.fail("unable to make path '{f}{s}': {s}", .{ | |
| 1002 | b.cache_root, output_sub_dir_path, @errorName(err), | |
| 1003 | }); | |
| 1004 | }; | |
| 1005 | const raw_output_path: Build.Cache.Path = .{ | |
| 1006 | .root_dir = b.cache_root, | |
| 1007 | .sub_path = b.pathJoin(&output_components), | |
| 1008 | }; | |
| 1009 | placeholder.output.generated_file.path = raw_output_path.toString(b.graph.arena) catch @panic("OOM"); | |
| 1010 | argv_list.items[placeholder.index] = b.fmt("{s}{s}", .{ | |
| 1011 | placeholder.output.prefix, | |
| 1012 | run.convertPathArg(raw_output_path), | |
| 1013 | }); | |
| 1014 | } | |
| 1015 | ||
| 1016 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, options, null); | |
| 1017 | ||
| 1018 | const dep_file_dir = Dir.cwd(); | |
| 1019 | const dep_file_basename = dep_output_file.generated_file.getPath2(b, step); | |
| 1020 | if (has_side_effects) | |
| 1021 | try man.addDepFile(dep_file_dir, dep_file_basename) | |
| 1022 | else | |
| 1023 | try man.addDepFilePost(dep_file_dir, dep_file_basename); | |
| 1024 | ||
| 1025 | const digest = if (has_side_effects) | |
| 1026 | man.hash.final() | |
| 1027 | else | |
| 1028 | man.final(); | |
| 1029 | ||
| 1030 | const any_output = output_placeholders.items.len > 0 or | |
| 1031 | run.captured_stdout != null or run.captured_stderr != null; | |
| 1032 | ||
| 1033 | // Rename into place | |
| 1034 | if (any_output) { | |
| 1035 | const o_sub_path = "o" ++ Dir.path.sep_str ++ &digest; | |
| 1036 | ||
| 1037 | b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| switch (err) { | |
| 1038 | Dir.RenameError.DirNotEmpty => { | |
| 1039 | b.cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| { | |
| 1040 | return step.fail("unable to remove dir '{f}'{s}: {t}", .{ | |
| 1041 | b.cache_root, tmp_dir_path, del_err, | |
| 1042 | }); | |
| 1043 | }; | |
| 1044 | b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |retry_err| { | |
| 1045 | return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ | |
| 1046 | b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, retry_err, | |
| 1047 | }); | |
| 1048 | }; | |
| 1049 | }, | |
| 1050 | else => return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ | |
| 1051 | b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err, | |
| 1052 | }), | |
| 1053 | }; | |
| 1054 | } | |
| 1055 | ||
| 1056 | if (!has_side_effects) try step.writeManifestAndWatch(&man); | |
| 1057 | ||
| 1058 | try populateGeneratedPaths( | |
| 1059 | arena, | |
| 1060 | output_placeholders.items, | |
| 1061 | run.captured_stdout, | |
| 1062 | run.captured_stderr, | |
| 1063 | b.cache_root, | |
| 1064 | &digest, | |
| 1065 | ); | |
| 1066 | } | |
| 1067 | ||
| 1068 | pub fn rerunInFuzzMode( | |
| 1069 | run: *Run, | |
| 1070 | fuzz: *std.Build.Fuzz, | |
| 1071 | prog_node: std.Progress.Node, | |
| 1072 | ) !void { | |
| 1073 | const step = &run.step; | |
| 1074 | const b = step.owner; | |
| 1075 | const io = b.graph.io; | |
| 1076 | const arena = b.allocator; | |
| 1077 | var argv_list: std.ArrayList([]const u8) = .empty; | |
| 1078 | for (run.argv.items) |arg| { | |
| 1079 | switch (arg) { | |
| 1080 | .bytes => |bytes| { | |
| 1081 | try argv_list.append(arena, bytes); | |
| 1082 | }, | |
| 1083 | .lazy_path => |file| { | |
| 1084 | const file_path = file.lazy_path.getPath3(b, step); | |
| 1085 | try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, run.convertPathArg(file_path) })); | |
| 1086 | }, | |
| 1087 | .decorated_directory => |dd| { | |
| 1088 | const file_path = dd.lazy_path.getPath3(b, step); | |
| 1089 | try argv_list.append(arena, b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix })); | |
| 1090 | }, | |
| 1091 | .file_content => |file_plp| { | |
| 1092 | const file_path = file_plp.lazy_path.getPath3(b, step); | |
| 1093 | ||
| 1094 | var result: std.Io.Writer.Allocating = .init(arena); | |
| 1095 | errdefer result.deinit(); | |
| 1096 | result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; | |
| 1097 | ||
| 1098 | const file = try file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}); | |
| 1099 | defer file.close(io); | |
| 1100 | ||
| 1101 | var buf: [1024]u8 = undefined; | |
| 1102 | var file_reader = file.reader(io, &buf); | |
| 1103 | _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { | |
| 1104 | error.ReadFailed => return file_reader.err.?, | |
| 1105 | error.WriteFailed => return error.OutOfMemory, | |
| 1106 | }; | |
| 1107 | ||
| 1108 | try argv_list.append(arena, result.written()); | |
| 1109 | }, | |
| 1110 | .artifact => |pa| { | |
| 1111 | const artifact = pa.artifact; | |
| 1112 | const file_path: []const u8 = p: { | |
| 1113 | if (artifact == run.producer.?) break :p b.fmt("{f}", .{run.rebuilt_executable.?}); | |
| 1114 | break :p artifact.installed_path orelse artifact.generated_bin.?.path.?; | |
| 1115 | }; | |
| 1116 | try argv_list.append(arena, b.fmt("{s}{s}", .{ | |
| 1117 | pa.prefix, | |
| 1118 | run.convertPathArg(.{ .root_dir = .cwd(), .sub_path = file_path }), | |
| 1119 | })); | |
| 1120 | }, | |
| 1121 | .output_file, .output_directory => unreachable, | |
| 1122 | } | |
| 1123 | } | |
| 1124 | ||
| 1125 | if (run.step.result_failed_command) |cmd| { | |
| 1126 | fuzz.gpa.free(cmd); | |
| 1127 | run.step.result_failed_command = null; | |
| 1128 | } | |
| 1129 | ||
| 1130 | const has_side_effects = false; | |
| 1131 | var rand_int: u64 = undefined; | |
| 1132 | io.random(@ptrCast(&rand_int)); | |
| 1133 | const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); | |
| 1134 | try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{ | |
| 1135 | .progress_node = prog_node, | |
| 1136 | .watch = undefined, // not used by `runCommand` | |
| 1137 | .web_server = null, // only needed for time reports | |
| 1138 | .unit_test_timeout_ns = null, // don't time out fuzz tests for now | |
| 1139 | .gpa = fuzz.gpa, | |
| 1140 | }, .{ | |
| 1141 | .fuzz = fuzz, | |
| 1142 | }); | |
| 1143 | } | |
| 1144 | ||
| 1145 | fn populateGeneratedPaths( | |
| 1146 | arena: std.mem.Allocator, | |
| 1147 | output_placeholders: []const IndexedOutput, | |
| 1148 | captured_stdout: ?*CapturedStdIo, | |
| 1149 | captured_stderr: ?*CapturedStdIo, | |
| 1150 | cache_root: Build.Cache.Directory, | |
| 1151 | digest: *const Build.Cache.HexDigest, | |
| 1152 | ) !void { | |
| 1153 | for (output_placeholders) |placeholder| { | |
| 1154 | placeholder.output.generated_file.path = try cache_root.join(arena, &.{ | |
| 1155 | "o", digest, placeholder.output.basename, | |
| 1156 | }); | |
| 1157 | } | |
| 1158 | ||
| 1159 | if (captured_stdout) |captured| { | |
| 1160 | captured.output.generated_file.path = try cache_root.join(arena, &.{ | |
| 1161 | "o", digest, captured.output.basename, | |
| 1162 | }); | |
| 1163 | } | |
| 1164 | ||
| 1165 | if (captured_stderr) |captured| { | |
| 1166 | captured.output.generated_file.path = try cache_root.join(arena, &.{ | |
| 1167 | "o", digest, captured.output.basename, | |
| 1168 | }); | |
| 1169 | } | |
| 1170 | } | |
| 1171 | ||
| 1172 | fn formatTerm(term: ?process.Child.Term, w: *std.Io.Writer) std.Io.Writer.Error!void { | |
| 1173 | if (term) |t| switch (t) { | |
| 1174 | .exited => |code| try w.print("exited with code {d}", .{code}), | |
| 1175 | .signal => |sig| try w.print("terminated with signal {t}", .{sig}), | |
| 1176 | .stopped => |sig| try w.print("stopped with signal {t}", .{sig}), | |
| 1177 | .unknown => |code| try w.print("terminated for unknown reason with code {d}", .{code}), | |
| 1178 | } else { | |
| 1179 | try w.writeAll("exited with any code"); | |
| 1180 | } | |
| 1181 | } | |
| 1182 | fn fmtTerm(term: ?process.Child.Term) std.fmt.Alt(?process.Child.Term, formatTerm) { | |
| 1183 | return .{ .data = term }; | |
| 1184 | } | |
| 1185 | ||
| 1186 | fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool { | |
| 1187 | return if (expected) |e| switch (e) { | |
| 1188 | .exited => |expected_code| switch (actual) { | |
| 1189 | .exited => |actual_code| expected_code == actual_code, | |
| 1190 | else => false, | |
| 1191 | }, | |
| 1192 | .signal => |expected_sig| switch (actual) { | |
| 1193 | .signal => |actual_sig| expected_sig == actual_sig, | |
| 1194 | else => false, | |
| 1195 | }, | |
| 1196 | .stopped => |expected_sig| switch (actual) { | |
| 1197 | .stopped => |actual_sig| expected_sig == actual_sig, | |
| 1198 | else => false, | |
| 1199 | }, | |
| 1200 | .unknown => |expected_code| switch (actual) { | |
| 1201 | .unknown => |actual_code| expected_code == actual_code, | |
| 1202 | else => false, | |
| 1203 | }, | |
| 1204 | } else switch (actual) { | |
| 1205 | .exited => true, | |
| 1206 | else => false, | |
| 1207 | }; | |
| 1208 | } | |
| 1209 | ||
| 1210 | const FuzzContext = struct { | |
| 1211 | fuzz: *std.Build.Fuzz, | |
| 1212 | }; | |
| 1213 | ||
| 1214 | fn runCommand( | |
| 1215 | run: *Run, | |
| 1216 | argv: []const []const u8, | |
| 1217 | has_side_effects: bool, | |
| 1218 | output_dir_path: []const u8, | |
| 1219 | options: Step.MakeOptions, | |
| 1220 | fuzz_context: ?FuzzContext, | |
| 1221 | ) !void { | |
| 1222 | const step = &run.step; | |
| 1223 | const b = step.owner; | |
| 1224 | const arena = b.allocator; | |
| 1225 | const gpa = options.gpa; | |
| 1226 | const io = b.graph.io; | |
| 1227 | ||
| 1228 | const cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, step) } else .inherit; | |
| 1229 | ||
| 1230 | try step.handleChildProcUnsupported(); | |
| 1231 | try Step.handleVerbose2(step.owner, cwd, run.environ_map, argv); | |
| 1232 | ||
| 1233 | const allow_skip = switch (run.stdio) { | |
| 1234 | .check, .zig_test => run.skip_foreign_checks, | |
| 1235 | else => false, | |
| 1236 | }; | |
| 1237 | ||
| 1238 | var interp_argv = std.array_list.Managed([]const u8).init(b.allocator); | |
| 1239 | defer interp_argv.deinit(); | |
| 1240 | ||
| 1241 | var environ_map: EnvMap = env: { | |
| 1242 | const orig = run.environ_map orelse &b.graph.environ_map; | |
| 1243 | break :env try orig.clone(gpa); | |
| 1244 | }; | |
| 1245 | defer environ_map.deinit(); | |
| 1246 | ||
| 1247 | const opt_generic_result = spawnChildAndCollect(run, argv, &environ_map, has_side_effects, options, fuzz_context) catch |err| term: { | |
| 1248 | // InvalidExe: cpu arch mismatch | |
| 1249 | // FileNotFound: can happen with a wrong dynamic linker path | |
| 1250 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { | |
| 1251 | // TODO: learn the target from the binary directly rather than from | |
| 1252 | // relying on it being a Compile step. This will make this logic | |
| 1253 | // work even for the edge case that the binary was produced by a | |
| 1254 | // third party. | |
| 1255 | const exe = switch (run.argv.items[0]) { | |
| 1256 | .artifact => |exe| exe.artifact, | |
| 1257 | else => break :interpret, | |
| 1258 | }; | |
| 1259 | switch (exe.kind) { | |
| 1260 | .exe, .@"test" => {}, | |
| 1261 | else => break :interpret, | |
| 1262 | } | |
| 1263 | ||
| 1264 | const root_target = exe.rootModuleTarget(); | |
| 1265 | const need_cross_libc = exe.is_linking_libc and | |
| 1266 | (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic)); | |
| 1267 | const other_target = exe.root_module.resolved_target.?.result; | |
| 1268 | switch (std.zig.system.getExternalExecutor(io, &b.graph.host.result, &other_target, .{ | |
| 1269 | .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null, | |
| 1270 | .link_libc = exe.is_linking_libc, | |
| 1271 | })) { | |
| 1272 | .native, .rosetta => { | |
| 1273 | if (allow_skip) return error.MakeSkipped; | |
| 1274 | break :interpret; | |
| 1275 | }, | |
| 1276 | .wine => |bin_name| { | |
| 1277 | if (b.enable_wine) { | |
| 1278 | try interp_argv.append(bin_name); | |
| 1279 | try interp_argv.appendSlice(argv); | |
| 1280 | ||
| 1281 | // Wine's excessive stderr logging is only situationally helpful. Disable it by default, but | |
| 1282 | // allow the user to override it (e.g. with `WINEDEBUG=err+all`) if desired. | |
| 1283 | if (environ_map.get("WINEDEBUG") == null) { | |
| 1284 | try environ_map.put("WINEDEBUG", "-all"); | |
| 1285 | } | |
| 1286 | } else { | |
| 1287 | return failForeign(run, "-fwine", argv[0], exe); | |
| 1288 | } | |
| 1289 | }, | |
| 1290 | .qemu => |bin_name| { | |
| 1291 | if (b.enable_qemu) { | |
| 1292 | try interp_argv.append(bin_name); | |
| 1293 | ||
| 1294 | if (need_cross_libc) { | |
| 1295 | if (b.libc_runtimes_dir) |dir| { | |
| 1296 | try interp_argv.append("-L"); | |
| 1297 | try interp_argv.append(b.pathJoin(&.{ | |
| 1298 | dir, | |
| 1299 | try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( | |
| 1300 | b.allocator, | |
| 1301 | root_target.cpu.arch, | |
| 1302 | root_target.os.tag, | |
| 1303 | root_target.abi, | |
| 1304 | ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple( | |
| 1305 | b.allocator, | |
| 1306 | root_target.cpu.arch, | |
| 1307 | root_target.abi, | |
| 1308 | ) else unreachable, | |
| 1309 | })); | |
| 1310 | } else return failForeign(run, "--libc-runtimes", argv[0], exe); | |
| 1311 | } | |
| 1312 | ||
| 1313 | try interp_argv.appendSlice(argv); | |
| 1314 | } else return failForeign(run, "-fqemu", argv[0], exe); | |
| 1315 | }, | |
| 1316 | .darling => |bin_name| { | |
| 1317 | if (b.enable_darling) { | |
| 1318 | try interp_argv.append(bin_name); | |
| 1319 | try interp_argv.appendSlice(argv); | |
| 1320 | } else { | |
| 1321 | return failForeign(run, "-fdarling", argv[0], exe); | |
| 1322 | } | |
| 1323 | }, | |
| 1324 | .wasmtime => |bin_name| { | |
| 1325 | if (b.enable_wasmtime) { | |
| 1326 | try interp_argv.append(bin_name); | |
| 1327 | try interp_argv.append("--dir=."); | |
| 1328 | // Wasmtime doeesn't inherit environment variables from the parent process | |
| 1329 | // by default. '-S inherit-env' was added in Wasmtime version 20. | |
| 1330 | try interp_argv.append("-Sinherit-env"); | |
| 1331 | try interp_argv.append(argv[0]); | |
| 1332 | try interp_argv.appendSlice(argv[1..]); | |
| 1333 | } else { | |
| 1334 | return failForeign(run, "-fwasmtime", argv[0], exe); | |
| 1335 | } | |
| 1336 | }, | |
| 1337 | .bad_dl => |foreign_dl| { | |
| 1338 | if (allow_skip) return error.MakeSkipped; | |
| 1339 | ||
| 1340 | const host_dl = b.graph.host.result.dynamic_linker.get() orelse "(none)"; | |
| 1341 | ||
| 1342 | return step.fail( | |
| 1343 | \\the host system is unable to execute binaries from the target | |
| 1344 | \\ because the host dynamic linker is '{s}', | |
| 1345 | \\ while the target dynamic linker is '{s}'. | |
| 1346 | \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step | |
| 1347 | , .{ host_dl, foreign_dl }); | |
| 1348 | }, | |
| 1349 | .bad_os_or_cpu => { | |
| 1350 | if (allow_skip) return error.MakeSkipped; | |
| 1351 | ||
| 1352 | const host_name = try b.graph.host.result.zigTriple(b.allocator); | |
| 1353 | const foreign_name = try root_target.zigTriple(b.allocator); | |
| 1354 | ||
| 1355 | return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ | |
| 1356 | host_name, foreign_name, | |
| 1357 | }); | |
| 1358 | }, | |
| 1359 | } | |
| 1360 | ||
| 1361 | if (root_target.os.tag == .windows) { | |
| 1362 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH | |
| 1363 | run.addPathForDynLibs(exe); | |
| 1364 | } | |
| 1365 | ||
| 1366 | gpa.free(step.result_failed_command.?); | |
| 1367 | step.result_failed_command = null; | |
| 1368 | try Step.handleVerbose2(step.owner, cwd, run.environ_map, interp_argv.items); | |
| 1369 | ||
| 1370 | break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| { | |
| 1371 | if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; | |
| 1372 | if (e == error.MakeFailed) return error.MakeFailed; // error already reported | |
| 1373 | return step.fail("unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e }); | |
| 1374 | }; | |
| 1375 | } | |
| 1376 | if (err == error.MakeFailed) return error.MakeFailed; // error already reported | |
| 1377 | ||
| 1378 | return step.fail("failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err }); | |
| 1379 | }; | |
| 1380 | ||
| 1381 | const generic_result = opt_generic_result orelse { | |
| 1382 | assert(run.stdio == .zig_test); | |
| 1383 | // Specific errors have already been reported, and test results are populated. All we need | |
| 1384 | // to do is report step failure if any test failed. | |
| 1385 | if (!step.test_results.isSuccess()) return error.MakeFailed; | |
| 1386 | return; | |
| 1387 | }; | |
| 1388 | ||
| 1389 | assert(fuzz_context == null); | |
| 1390 | assert(run.stdio != .zig_test); | |
| 1391 | ||
| 1392 | // Capture stdout and stderr to GeneratedFile objects. | |
| 1393 | const Stream = struct { | |
| 1394 | captured: ?*CapturedStdIo, | |
| 1395 | bytes: ?[]const u8, | |
| 1396 | }; | |
| 1397 | for ([_]Stream{ | |
| 1398 | .{ | |
| 1399 | .captured = run.captured_stdout, | |
| 1400 | .bytes = generic_result.stdout, | |
| 1401 | }, | |
| 1402 | .{ | |
| 1403 | .captured = run.captured_stderr, | |
| 1404 | .bytes = generic_result.stderr, | |
| 1405 | }, | |
| 1406 | }) |stream| { | |
| 1407 | if (stream.captured) |captured| { | |
| 1408 | const output_components = .{ output_dir_path, captured.output.basename }; | |
| 1409 | const output_path = try b.cache_root.join(arena, &output_components); | |
| 1410 | captured.output.generated_file.path = output_path; | |
| 1411 | ||
| 1412 | const sub_path = b.pathJoin(&output_components); | |
| 1413 | const sub_path_dirname = Dir.path.dirname(sub_path).?; | |
| 1414 | b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { | |
| 1415 | return step.fail("unable to make path '{f}{s}': {s}", .{ | |
| 1416 | b.cache_root, sub_path_dirname, @errorName(err), | |
| 1417 | }); | |
| 1418 | }; | |
| 1419 | const data = switch (captured.trim_whitespace) { | |
| 1420 | .none => stream.bytes.?, | |
| 1421 | .all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace), | |
| 1422 | .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace), | |
| 1423 | .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace), | |
| 1424 | }; | |
| 1425 | b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = data }) catch |err| { | |
| 1426 | return step.fail("unable to write file '{f}{s}': {s}", .{ | |
| 1427 | b.cache_root, sub_path, @errorName(err), | |
| 1428 | }); | |
| 1429 | }; | |
| 1430 | } | |
| 1431 | } | |
| 1432 | ||
| 1433 | switch (run.stdio) { | |
| 1434 | .zig_test => unreachable, | |
| 1435 | .check => |checks| for (checks.items) |check| switch (check) { | |
| 1436 | .expect_stderr_exact => |expected_bytes| { | |
| 1437 | if (!mem.eql(u8, expected_bytes, generic_result.stderr.?)) { | |
| 1438 | return step.fail( | |
| 1439 | \\========= expected this stderr: ========= | |
| 1440 | \\{s} | |
| 1441 | \\========= but found: ==================== | |
| 1442 | \\{s} | |
| 1443 | , .{ | |
| 1444 | expected_bytes, | |
| 1445 | generic_result.stderr.?, | |
| 1446 | }); | |
| 1447 | } | |
| 1448 | }, | |
| 1449 | .expect_stderr_match => |match| { | |
| 1450 | if (mem.find(u8, generic_result.stderr.?, match) == null) { | |
| 1451 | return step.fail( | |
| 1452 | \\========= expected to find in stderr: ========= | |
| 1453 | \\{s} | |
| 1454 | \\========= but stderr does not contain it: ===== | |
| 1455 | \\{s} | |
| 1456 | , .{ | |
| 1457 | match, | |
| 1458 | generic_result.stderr.?, | |
| 1459 | }); | |
| 1460 | } | |
| 1461 | }, | |
| 1462 | .expect_stdout_exact => |expected_bytes| { | |
| 1463 | if (!mem.eql(u8, expected_bytes, generic_result.stdout.?)) { | |
| 1464 | return step.fail( | |
| 1465 | \\========= expected this stdout: ========= | |
| 1466 | \\{s} | |
| 1467 | \\========= but found: ==================== | |
| 1468 | \\{s} | |
| 1469 | , .{ | |
| 1470 | expected_bytes, | |
| 1471 | generic_result.stdout.?, | |
| 1472 | }); | |
| 1473 | } | |
| 1474 | }, | |
| 1475 | .expect_stdout_match => |match| { | |
| 1476 | if (mem.find(u8, generic_result.stdout.?, match) == null) { | |
| 1477 | return step.fail( | |
| 1478 | \\========= expected to find in stdout: ========= | |
| 1479 | \\{s} | |
| 1480 | \\========= but stdout does not contain it: ===== | |
| 1481 | \\{s} | |
| 1482 | , .{ | |
| 1483 | match, | |
| 1484 | generic_result.stdout.?, | |
| 1485 | }); | |
| 1486 | } | |
| 1487 | }, | |
| 1488 | .expect_term => |expected_term| { | |
| 1489 | if (!termMatches(expected_term, generic_result.term)) { | |
| 1490 | return step.fail("process {f} (expected {f})", .{ | |
| 1491 | fmtTerm(generic_result.term), | |
| 1492 | fmtTerm(expected_term), | |
| 1493 | }); | |
| 1494 | } | |
| 1495 | }, | |
| 1496 | }, | |
| 1497 | else => { | |
| 1498 | // On failure, report captured stderr like normal standard error output. | |
| 1499 | const bad_exit = switch (generic_result.term) { | |
| 1500 | .exited => |code| code != 0, | |
| 1501 | .signal, .stopped, .unknown => true, | |
| 1502 | }; | |
| 1503 | if (bad_exit) { | |
| 1504 | if (generic_result.stderr) |bytes| { | |
| 1505 | run.step.result_stderr = bytes; | |
| 1506 | } | |
| 1507 | } | |
| 1508 | ||
| 1509 | try step.handleChildProcessTerm(generic_result.term); | |
| 1510 | }, | |
| 1511 | } | |
| 1512 | } | |
| 1513 | ||
| 1514 | const EvalGenericResult = struct { | |
| 1515 | term: process.Child.Term, | |
| 1516 | stdout: ?[]const u8, | |
| 1517 | stderr: ?[]const u8, | |
| 1518 | }; | |
| 1519 | ||
| 1520 | fn spawnChildAndCollect( | |
| 1521 | run: *Run, | |
| 1522 | argv: []const []const u8, | |
| 1523 | environ_map: *EnvMap, | |
| 1524 | has_side_effects: bool, | |
| 1525 | options: Step.MakeOptions, | |
| 1526 | fuzz_context: ?FuzzContext, | |
| 1527 | ) !?EvalGenericResult { | |
| 1528 | const b = run.step.owner; | |
| 1529 | const graph = b.graph; | |
| 1530 | const io = graph.io; | |
| 1531 | ||
| 1532 | if (fuzz_context != null) { | |
| 1533 | assert(!has_side_effects); | |
| 1534 | assert(run.stdio == .zig_test); | |
| 1535 | } | |
| 1536 | ||
| 1537 | const child_cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, &run.step) } else .inherit; | |
| 1538 | ||
| 1539 | // If an error occurs, it's caused by this command: | |
| 1540 | assert(run.step.result_failed_command == null); | |
| 1541 | run.step.result_failed_command = try Step.allocPrintCmd(options.gpa, child_cwd, .{ | |
| 1542 | .child = environ_map, | |
| 1543 | .parent = &graph.environ_map, | |
| 1544 | }, argv); | |
| 1545 | ||
| 1546 | var spawn_options: process.SpawnOptions = .{ | |
| 1547 | .argv = argv, | |
| 1548 | .cwd = child_cwd, | |
| 1549 | .environ_map = environ_map, | |
| 1550 | .request_resource_usage_statistics = true, | |
| 1551 | .stdin = if (run.stdin != .none) s: { | |
| 1552 | assert(run.stdio != .inherit); | |
| 1553 | break :s .pipe; | |
| 1554 | } else switch (run.stdio) { | |
| 1555 | .infer_from_args => if (has_side_effects) .inherit else .ignore, | |
| 1556 | .inherit => .inherit, | |
| 1557 | .check => .ignore, | |
| 1558 | .zig_test => .pipe, | |
| 1559 | }, | |
| 1560 | .stdout = if (run.captured_stdout != null) .pipe else switch (run.stdio) { | |
| 1561 | .infer_from_args => if (has_side_effects) .inherit else .ignore, | |
| 1562 | .inherit => .inherit, | |
| 1563 | .check => |checks| if (checksContainStdout(checks.items)) .pipe else .ignore, | |
| 1564 | .zig_test => .pipe, | |
| 1565 | }, | |
| 1566 | .stderr = if (run.captured_stderr != null) .pipe else switch (run.stdio) { | |
| 1567 | .infer_from_args => if (has_side_effects) .inherit else .pipe, | |
| 1568 | .inherit => .inherit, | |
| 1569 | .check => .pipe, | |
| 1570 | .zig_test => .pipe, | |
| 1571 | }, | |
| 1572 | }; | |
| 1573 | ||
| 1574 | if (run.stdio == .zig_test) { | |
| 1575 | const started: Io.Clock.Timestamp = .now(io, .awake); | |
| 1576 | const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) { | |
| 1577 | error.Canceled => |e| return e, | |
| 1578 | else => |e| e, | |
| 1579 | }; | |
| 1580 | run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); | |
| 1581 | try result; | |
| 1582 | return null; | |
| 1583 | } else { | |
| 1584 | const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; | |
| 1585 | if (!run.disable_zig_progress and !inherit) { | |
| 1586 | spawn_options.progress_node = options.progress_node; | |
| 1587 | } | |
| 1588 | const terminal_mode: Io.Terminal.Mode = if (inherit) m: { | |
| 1589 | const stderr = try io.lockStderr(&.{}, graph.stderr_mode); | |
| 1590 | break :m stderr.terminal_mode; | |
| 1591 | } else .no_color; | |
| 1592 | defer if (inherit) io.unlockStderr(); | |
| 1593 | try setColorEnvironmentVariables(run, environ_map, terminal_mode); | |
| 1594 | ||
| 1595 | const started: Io.Clock.Timestamp = .now(io, .awake); | |
| 1596 | const result = evalGeneric(run, spawn_options) catch |err| switch (err) { | |
| 1597 | error.Canceled => |e| return e, | |
| 1598 | else => |e| e, | |
| 1599 | }; | |
| 1600 | run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); | |
| 1601 | return try result; | |
| 1602 | } | |
| 1603 | } | |
| 1604 | ||
| 1605 | fn setColorEnvironmentVariables(run: *Run, environ_map: *EnvMap, terminal_mode: Io.Terminal.Mode) !void { | |
| 1606 | color: switch (run.color) { | |
| 1607 | .manual => {}, | |
| 1608 | .enable => { | |
| 1609 | try environ_map.put("CLICOLOR_FORCE", "1"); | |
| 1610 | _ = environ_map.swapRemove("NO_COLOR"); | |
| 1611 | }, | |
| 1612 | .disable => { | |
| 1613 | try environ_map.put("NO_COLOR", "1"); | |
| 1614 | _ = environ_map.swapRemove("CLICOLOR_FORCE"); | |
| 1615 | }, | |
| 1616 | .inherit => switch (terminal_mode) { | |
| 1617 | .no_color, .windows_api => continue :color .disable, | |
| 1618 | .escape_codes => continue :color .enable, | |
| 1619 | }, | |
| 1620 | .auto => { | |
| 1621 | const capture_stderr = run.captured_stderr != null or switch (run.stdio) { | |
| 1622 | .check => |checks| checksContainStderr(checks.items), | |
| 1623 | .infer_from_args, .inherit, .zig_test => false, | |
| 1624 | }; | |
| 1625 | if (capture_stderr) { | |
| 1626 | continue :color .disable; | |
| 1627 | } else { | |
| 1628 | continue :color .inherit; | |
| 1629 | } | |
| 1630 | }, | |
| 1631 | } | |
| 1632 | } | |
| 1633 | ||
| 1634 | const StdioPollEnum = enum { stdout, stderr }; | |
| 1635 | ||
| 1636 | fn evalZigTest( | |
| 1637 | run: *Run, | |
| 1638 | spawn_options: process.SpawnOptions, | |
| 1639 | options: Step.MakeOptions, | |
| 1640 | fuzz_context: ?FuzzContext, | |
| 1641 | ) !void { | |
| 1642 | if (fuzz_context != null) { | |
| 1643 | try evalFuzzTest(run, spawn_options, options, fuzz_context.?); | |
| 1644 | return; | |
| 1645 | } | |
| 1646 | ||
| 1647 | const step_owner = run.step.owner; | |
| 1648 | const gpa = step_owner.allocator; | |
| 1649 | const arena = step_owner.allocator; | |
| 1650 | const io = step_owner.graph.io; | |
| 1651 | ||
| 1652 | // We will update this every time a child runs. | |
| 1653 | run.step.result_peak_rss = 0; | |
| 1654 | ||
| 1655 | var test_results: Step.TestResults = .{ | |
| 1656 | .test_count = 0, | |
| 1657 | .skip_count = 0, | |
| 1658 | .fail_count = 0, | |
| 1659 | .crash_count = 0, | |
| 1660 | .timeout_count = 0, | |
| 1661 | .leak_count = 0, | |
| 1662 | .log_err_count = 0, | |
| 1663 | }; | |
| 1664 | var test_metadata: ?TestMetadata = null; | |
| 1665 | ||
| 1666 | while (true) { | |
| 1667 | var child = try process.spawn(io, spawn_options); | |
| 1668 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 1669 | var multi_reader: Io.File.MultiReader = undefined; | |
| 1670 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); | |
| 1671 | var child_killed = false; | |
| 1672 | defer if (!child_killed) { | |
| 1673 | child.kill(io); | |
| 1674 | multi_reader.deinit(); | |
| 1675 | run.step.result_peak_rss = @max( | |
| 1676 | run.step.result_peak_rss, | |
| 1677 | child.resource_usage_statistics.getMaxRss() orelse 0, | |
| 1678 | ); | |
| 1679 | }; | |
| 1680 | ||
| 1681 | switch (try waitZigTest( | |
| 1682 | run, | |
| 1683 | &child, | |
| 1684 | options, | |
| 1685 | &multi_reader, | |
| 1686 | &test_metadata, | |
| 1687 | &test_results, | |
| 1688 | )) { | |
| 1689 | .write_failed => |err| { | |
| 1690 | // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured | |
| 1691 | // all available stderr to make our error output as useful as possible. | |
| 1692 | const stderr_fr = multi_reader.fileReader(1); | |
| 1693 | while (stderr_fr.interface.fillMore()) |_| {} else |e| switch (e) { | |
| 1694 | error.ReadFailed => return stderr_fr.err.?, | |
| 1695 | error.EndOfStream => {}, | |
| 1696 | } | |
| 1697 | run.step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); | |
| 1698 | ||
| 1699 | // Clean up everything and wait for the child to exit. | |
| 1700 | child.stdin.?.close(io); | |
| 1701 | child.stdin = null; | |
| 1702 | multi_reader.deinit(); | |
| 1703 | child_killed = true; | |
| 1704 | const term = try child.wait(io); | |
| 1705 | run.step.result_peak_rss = @max( | |
| 1706 | run.step.result_peak_rss, | |
| 1707 | child.resource_usage_statistics.getMaxRss() orelse 0, | |
| 1708 | ); | |
| 1709 | ||
| 1710 | // The individual unit test results are irrelevant: the test runner itself broke! | |
| 1711 | // Fail immediately without populating `s.test_results`. | |
| 1712 | return run.step.fail("unable to write stdin ({t}); test process unexpectedly {f}", .{ err, fmtTerm(term) }); | |
| 1713 | }, | |
| 1714 | .no_poll => |no_poll| { | |
| 1715 | // This might be a success (we requested exit and the child dutifully closed stdout) or | |
| 1716 | // a crash of some kind. Either way, the child will terminate by itself -- wait for it. | |
| 1717 | const stderr_reader = multi_reader.reader(1); | |
| 1718 | const stderr_owned = try arena.dupe(u8, stderr_reader.buffered()); | |
| 1719 | ||
| 1720 | // Clean up everything and wait for the child to exit. | |
| 1721 | child.stdin.?.close(io); | |
| 1722 | child.stdin = null; | |
| 1723 | multi_reader.deinit(); | |
| 1724 | child_killed = true; | |
| 1725 | const term = try child.wait(io); | |
| 1726 | run.step.result_peak_rss = @max( | |
| 1727 | run.step.result_peak_rss, | |
| 1728 | child.resource_usage_statistics.getMaxRss() orelse 0, | |
| 1729 | ); | |
| 1730 | ||
| 1731 | if (no_poll.active_test_index) |test_index| { | |
| 1732 | // A test was running, so this is definitely a crash. Report it against that | |
| 1733 | // test, and continue to the next test. | |
| 1734 | test_metadata.?.ns_per_test[test_index] = no_poll.ns_elapsed; | |
| 1735 | test_results.crash_count += 1; | |
| 1736 | try run.step.addError("'{s}' {f}{s}{s}", .{ | |
| 1737 | test_metadata.?.testName(test_index), | |
| 1738 | fmtTerm(term), | |
| 1739 | if (stderr_owned.len != 0) " with stderr:\n" else "", | |
| 1740 | std.mem.trim(u8, stderr_owned, "\n"), | |
| 1741 | }); | |
| 1742 | continue; | |
| 1743 | } | |
| 1744 | ||
| 1745 | // Report an error if the child terminated uncleanly or if we were still trying to run more tests. | |
| 1746 | run.step.result_stderr = stderr_owned; | |
| 1747 | const tests_done = test_metadata != null and test_metadata.?.next_index == std.math.maxInt(u32); | |
| 1748 | if (!tests_done or !termMatches(.{ .exited = 0 }, term)) { | |
| 1749 | // The individual unit test results are irrelevant: the test runner itself broke! | |
| 1750 | // Fail immediately without populating `s.test_results`. | |
| 1751 | return run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)}); | |
| 1752 | } | |
| 1753 | ||
| 1754 | // We're done with all of the tests! Commit the test results and return. | |
| 1755 | run.step.test_results = test_results; | |
| 1756 | if (test_metadata) |tm| { | |
| 1757 | run.cached_test_metadata = tm.toCachedTestMetadata(); | |
| 1758 | if (options.web_server) |ws| { | |
| 1759 | if (run.step.owner.graph.time_report) { | |
| 1760 | ws.updateTimeReportRunTest( | |
| 1761 | run, | |
| 1762 | &run.cached_test_metadata.?, | |
| 1763 | tm.ns_per_test, | |
| 1764 | ); | |
| 1765 | } | |
| 1766 | } | |
| 1767 | } | |
| 1768 | return; | |
| 1769 | }, | |
| 1770 | .timeout => |timeout| { | |
| 1771 | const stderr_reader = multi_reader.reader(1); | |
| 1772 | const stderr = stderr_reader.buffered(); | |
| 1773 | stderr_reader.tossBuffered(); | |
| 1774 | if (timeout.active_test_index) |test_index| { | |
| 1775 | // A test was running. Report the timeout against that test, and continue on to | |
| 1776 | // the next test. | |
| 1777 | test_metadata.?.ns_per_test[test_index] = timeout.ns_elapsed; | |
| 1778 | test_results.timeout_count += 1; | |
| 1779 | try run.step.addError("'{s}' timed out after {f}{s}{s}", .{ | |
| 1780 | test_metadata.?.testName(test_index), | |
| 1781 | Io.Duration{ .nanoseconds = timeout.ns_elapsed }, | |
| 1782 | if (stderr.len != 0) " with stderr:\n" else "", | |
| 1783 | std.mem.trim(u8, stderr, "\n"), | |
| 1784 | }); | |
| 1785 | continue; | |
| 1786 | } | |
| 1787 | // Just log an error and let the child be killed. | |
| 1788 | run.step.result_stderr = try arena.dupe(u8, stderr); | |
| 1789 | // The individual unit test results in `results` are irrelevant: the test runner | |
| 1790 | // is broken! Fail immediately without populating `s.test_results`. | |
| 1791 | return run.step.fail("test runner failed to respond for {f}", .{Io.Duration{ .nanoseconds = timeout.ns_elapsed }}); | |
| 1792 | }, | |
| 1793 | } | |
| 1794 | comptime unreachable; | |
| 1795 | } | |
| 1796 | } | |
| 1797 | ||
| 1798 | /// Reads stdout of a Zig test process until a termination condition is reached: | |
| 1799 | /// * A write fails, indicating the child unexpectedly closed stdin | |
| 1800 | /// * A test (or a response from the test runner) times out | |
| 1801 | /// * The wait fails, indicating the child closed stdout and stderr | |
| 1802 | fn waitZigTest( | |
| 1803 | run: *Run, | |
| 1804 | child: *process.Child, | |
| 1805 | options: Step.MakeOptions, | |
| 1806 | multi_reader: *Io.File.MultiReader, | |
| 1807 | opt_metadata: *?TestMetadata, | |
| 1808 | results: *Step.TestResults, | |
| 1809 | ) !union(enum) { | |
| 1810 | write_failed: anyerror, | |
| 1811 | no_poll: struct { | |
| 1812 | active_test_index: ?u32, | |
| 1813 | ns_elapsed: u64, | |
| 1814 | }, | |
| 1815 | timeout: struct { | |
| 1816 | active_test_index: ?u32, | |
| 1817 | ns_elapsed: u64, | |
| 1818 | }, | |
| 1819 | } { | |
| 1820 | const gpa = run.step.owner.allocator; | |
| 1821 | const arena = run.step.owner.allocator; | |
| 1822 | const io = run.step.owner.graph.io; | |
| 1823 | ||
| 1824 | var sub_prog_node: ?std.Progress.Node = null; | |
| 1825 | defer if (sub_prog_node) |n| n.end(); | |
| 1826 | ||
| 1827 | if (opt_metadata.*) |*md| { | |
| 1828 | // Previous unit test process died or was killed; we're continuing where it left off | |
| 1829 | requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 1830 | } else { | |
| 1831 | // Running unit tests normally | |
| 1832 | run.fuzz_tests.clearRetainingCapacity(); | |
| 1833 | sendMessage(io, child.stdin.?, .query_test_metadata) catch |err| return .{ .write_failed = err }; | |
| 1834 | } | |
| 1835 | ||
| 1836 | var active_test_index: ?u32 = null; | |
| 1837 | ||
| 1838 | var last_update: Io.Clock.Timestamp = .now(io, .awake); | |
| 1839 | ||
| 1840 | // This timeout is used when we're waiting on the test runner itself rather than a user-specified | |
| 1841 | // test. For instance, if the test runner leaves this much time between us requesting a test to | |
| 1842 | // start and it acknowledging the test starting, we terminate the child and raise an error. This | |
| 1843 | // *should* never happen, but could in theory be caused by some very unlucky IB in a test. | |
| 1844 | const response_timeout: Io.Clock.Duration = t: { | |
| 1845 | const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); | |
| 1846 | break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) }; | |
| 1847 | }; | |
| 1848 | const test_timeout: ?Io.Clock.Duration = if (options.unit_test_timeout_ns) |ns| .{ | |
| 1849 | .clock = .awake, | |
| 1850 | .raw = .fromNanoseconds(ns), | |
| 1851 | } else null; | |
| 1852 | ||
| 1853 | const stdout = multi_reader.reader(0); | |
| 1854 | const stderr = multi_reader.reader(1); | |
| 1855 | const Header = std.zig.Server.Message.Header; | |
| 1856 | ||
| 1857 | while (true) { | |
| 1858 | const timeout: Io.Timeout = t: { | |
| 1859 | const opt_duration = if (active_test_index == null) response_timeout else test_timeout; | |
| 1860 | const duration = opt_duration orelse break :t .none; | |
| 1861 | break :t .{ .deadline = last_update.addDuration(duration) }; | |
| 1862 | }; | |
| 1863 | ||
| 1864 | // This block is exited when `stdout` contains enough bytes for a `Header`. | |
| 1865 | header_ready: { | |
| 1866 | if (stdout.buffered().len >= @sizeOf(Header)) { | |
| 1867 | // We already have one, no need to poll! | |
| 1868 | break :header_ready; | |
| 1869 | } | |
| 1870 | ||
| 1871 | multi_reader.fill(64, timeout) catch |err| switch (err) { | |
| 1872 | error.Timeout => return .{ .timeout = .{ | |
| 1873 | .active_test_index = active_test_index, | |
| 1874 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 1875 | } }, | |
| 1876 | error.EndOfStream => return .{ .no_poll = .{ | |
| 1877 | .active_test_index = active_test_index, | |
| 1878 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 1879 | } }, | |
| 1880 | else => |e| return e, | |
| 1881 | }; | |
| 1882 | ||
| 1883 | continue; | |
| 1884 | } | |
| 1885 | // There is definitely a header available now -- read it. | |
| 1886 | const header = stdout.takeStruct(Header, .little) catch unreachable; | |
| 1887 | ||
| 1888 | while (stdout.buffered().len < header.bytes_len) { | |
| 1889 | multi_reader.fill(64, timeout) catch |err| switch (err) { | |
| 1890 | error.Timeout => return .{ .timeout = .{ | |
| 1891 | .active_test_index = active_test_index, | |
| 1892 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 1893 | } }, | |
| 1894 | error.EndOfStream => return .{ .no_poll = .{ | |
| 1895 | .active_test_index = active_test_index, | |
| 1896 | .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), | |
| 1897 | } }, | |
| 1898 | else => |e| return e, | |
| 1899 | }; | |
| 1900 | } | |
| 1901 | ||
| 1902 | const body = stdout.take(header.bytes_len) catch unreachable; | |
| 1903 | var body_r: std.Io.Reader = .fixed(body); | |
| 1904 | switch (header.tag) { | |
| 1905 | .zig_version => { | |
| 1906 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) return run.step.fail( | |
| 1907 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 1908 | .{ builtin.zig_version_string, body }, | |
| 1909 | ); | |
| 1910 | }, | |
| 1911 | .test_metadata => { | |
| 1912 | // `metadata` would only be populated if we'd already seen a `test_metadata`, but we | |
| 1913 | // only request it once (and importantly, we don't re-request it if we kill and | |
| 1914 | // restart the test runner). | |
| 1915 | assert(opt_metadata.* == null); | |
| 1916 | ||
| 1917 | const tm_hdr = body_r.takeStruct(std.zig.Server.Message.TestMetadata, .little) catch unreachable; | |
| 1918 | results.test_count = tm_hdr.tests_len; | |
| 1919 | ||
| 1920 | const names = try arena.alloc(u32, results.test_count); | |
| 1921 | for (names) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable; | |
| 1922 | ||
| 1923 | const expected_panic_msgs = try arena.alloc(u32, results.test_count); | |
| 1924 | for (expected_panic_msgs) |*dest| dest.* = body_r.takeInt(u32, .little) catch unreachable; | |
| 1925 | ||
| 1926 | const string_bytes = body_r.take(tm_hdr.string_bytes_len) catch unreachable; | |
| 1927 | ||
| 1928 | options.progress_node.setEstimatedTotalItems(names.len); | |
| 1929 | opt_metadata.* = .{ | |
| 1930 | .string_bytes = try arena.dupe(u8, string_bytes), | |
| 1931 | .ns_per_test = try arena.alloc(u64, results.test_count), | |
| 1932 | .names = names, | |
| 1933 | .expected_panic_msgs = expected_panic_msgs, | |
| 1934 | .next_index = 0, | |
| 1935 | .prog_node = options.progress_node, | |
| 1936 | }; | |
| 1937 | @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); | |
| 1938 | ||
| 1939 | active_test_index = null; | |
| 1940 | last_update = .now(io, .awake); | |
| 1941 | ||
| 1942 | requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 1943 | }, | |
| 1944 | .test_started => { | |
| 1945 | active_test_index = opt_metadata.*.?.next_index - 1; | |
| 1946 | last_update = .now(io, .awake); | |
| 1947 | }, | |
| 1948 | .test_results => { | |
| 1949 | const md = &opt_metadata.*.?; | |
| 1950 | ||
| 1951 | const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable; | |
| 1952 | assert(tr_hdr.index == active_test_index); | |
| 1953 | ||
| 1954 | switch (tr_hdr.flags.status) { | |
| 1955 | .pass => {}, | |
| 1956 | .skip => results.skip_count +|= 1, | |
| 1957 | .fail => results.fail_count +|= 1, | |
| 1958 | } | |
| 1959 | const leak_count = tr_hdr.flags.leak_count; | |
| 1960 | const log_err_count = tr_hdr.flags.log_err_count; | |
| 1961 | results.leak_count +|= leak_count; | |
| 1962 | results.log_err_count +|= log_err_count; | |
| 1963 | ||
| 1964 | if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, md.testName(tr_hdr.index)); | |
| 1965 | ||
| 1966 | if (tr_hdr.flags.status == .fail) { | |
| 1967 | const name = md.testName(tr_hdr.index); | |
| 1968 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); | |
| 1969 | stderr.tossBuffered(); | |
| 1970 | if (stderr_bytes.len == 0) { | |
| 1971 | try run.step.addError("'{s}' failed without output", .{name}); | |
| 1972 | } else { | |
| 1973 | try run.step.addError("'{s}' failed:\n{s}", .{ name, stderr_bytes }); | |
| 1974 | } | |
| 1975 | } else if (leak_count > 0) { | |
| 1976 | const name = md.testName(tr_hdr.index); | |
| 1977 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); | |
| 1978 | stderr.tossBuffered(); | |
| 1979 | try run.step.addError("'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes }); | |
| 1980 | } else if (log_err_count > 0) { | |
| 1981 | const name = md.testName(tr_hdr.index); | |
| 1982 | const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n"); | |
| 1983 | stderr.tossBuffered(); | |
| 1984 | try run.step.addError("'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes }); | |
| 1985 | } | |
| 1986 | ||
| 1987 | active_test_index = null; | |
| 1988 | ||
| 1989 | const now: Io.Clock.Timestamp = .now(io, .awake); | |
| 1990 | md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds); | |
| 1991 | last_update = now; | |
| 1992 | ||
| 1993 | requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; | |
| 1994 | }, | |
| 1995 | else => {}, // ignore other messages | |
| 1996 | } | |
| 1997 | } | |
| 1998 | } | |
| 1999 | ||
| 2000 | const FuzzTestRunner = struct { | |
| 2001 | run: *Run, | |
| 2002 | ctx: FuzzContext, | |
| 2003 | coverage_id: ?u64, | |
| 2004 | ||
| 2005 | instances: []Instance, | |
| 2006 | /// The indexes of this are layed out such that it is effectively an array | |
| 2007 | /// of `[instances.len][3]Io.Operation.Storage` of stdin, stdout, stderr. | |
| 2008 | batch: Io.Batch, | |
| 2009 | /// LIFO. Stream of message bodies trailed by PendingBroadcastFooter. | |
| 2010 | pending_broadcasts: std.ArrayList(u8), | |
| 2011 | broadcast: std.ArrayList(u8), | |
| 2012 | broadcast_undelivered: u32, | |
| 2013 | ||
| 2014 | const Instance = struct { | |
| 2015 | child: process.Child, | |
| 2016 | message: std.ArrayListAligned(u8, .@"4"), | |
| 2017 | broadcast_written: usize, | |
| 2018 | stderr: std.ArrayList(u8), | |
| 2019 | stdin_vec: [1][]u8, | |
| 2020 | stdout_vec: [1][]u8, | |
| 2021 | stderr_vec: [1][]u8, | |
| 2022 | progress_node: std.Progress.Node, | |
| 2023 | ||
| 2024 | fn messageHeader(instance: *Instance) InHeader { | |
| 2025 | assert(instance.message.items.len >= @sizeOf(InHeader)); | |
| 2026 | const header_ptr: *InHeader = @ptrCast(instance.message.items); | |
| 2027 | var header = header_ptr.*; | |
| 2028 | if (std.builtin.Endian.native != .little) { | |
| 2029 | std.mem.byteSwapAllFields(InHeader, &header); | |
| 2030 | } | |
| 2031 | return header; | |
| 2032 | } | |
| 2033 | }; | |
| 2034 | ||
| 2035 | const PendingBroadcastFooter = struct { | |
| 2036 | from_id: u32, | |
| 2037 | body_len: u32, | |
| 2038 | }; | |
| 2039 | ||
| 2040 | const InHeader = std.zig.Server.Message.Header; | |
| 2041 | const OutHeader = std.zig.Client.Message.Header; | |
| 2042 | ||
| 2043 | const stdin_i = 0; | |
| 2044 | const stdout_i = 1; | |
| 2045 | const stderr_i = 2; | |
| 2046 | ||
| 2047 | fn init( | |
| 2048 | run: *Run, | |
| 2049 | ctx: FuzzContext, | |
| 2050 | progress_node: std.Progress.Node, | |
| 2051 | spawn_options: process.SpawnOptions, | |
| 2052 | ) !FuzzTestRunner { | |
| 2053 | const step_owner = run.step.owner; | |
| 2054 | const gpa = step_owner.allocator; | |
| 2055 | const io = step_owner.graph.io; | |
| 2056 | ||
| 2057 | const n_instances = switch (ctx.fuzz.mode) { | |
| 2058 | .forever => step_owner.graph.max_jobs orelse @min( | |
| 2059 | std.Thread.getCpuCount() catch 1, | |
| 2060 | (std.math.maxInt(u32) - 2) / 3, | |
| 2061 | ), | |
| 2062 | .limit => 1, | |
| 2063 | }; | |
| 2064 | const instances = try gpa.alloc(Instance, n_instances); | |
| 2065 | errdefer gpa.free(instances); | |
| 2066 | const batch_storage = try gpa.alloc(Io.Operation.Storage, instances.len * 3); | |
| 2067 | errdefer gpa.free(batch_storage); | |
| 2068 | ||
| 2069 | @memset(instances, .{ | |
| 2070 | .child = undefined, | |
| 2071 | .message = .empty, | |
| 2072 | .broadcast_written = undefined, | |
| 2073 | .stderr = .empty, | |
| 2074 | .stdin_vec = undefined, | |
| 2075 | .stdout_vec = undefined, | |
| 2076 | .stderr_vec = undefined, | |
| 2077 | .progress_node = undefined, | |
| 2078 | }); | |
| 2079 | for (0.., instances) |id, *instance| { | |
| 2080 | errdefer for (instances[0..id]) |*spawned| { | |
| 2081 | spawned.child.kill(io); | |
| 2082 | spawned.progress_node.end(); | |
| 2083 | }; | |
| 2084 | instance.child = try process.spawn(io, spawn_options); | |
| 2085 | instance.progress_node = progress_node.start("starting fuzzer", 0); | |
| 2086 | } | |
| 2087 | ||
| 2088 | return .{ | |
| 2089 | .run = run, | |
| 2090 | .ctx = ctx, | |
| 2091 | .coverage_id = null, | |
| 2092 | ||
| 2093 | .instances = instances, | |
| 2094 | .batch = .init(batch_storage), | |
| 2095 | .pending_broadcasts = .empty, | |
| 2096 | .broadcast = .empty, | |
| 2097 | .broadcast_undelivered = 0, | |
| 2098 | }; | |
| 2099 | } | |
| 2100 | ||
| 2101 | fn deinit(f: *FuzzTestRunner) void { | |
| 2102 | const step_owner = f.run.step.owner; | |
| 2103 | const gpa = step_owner.allocator; | |
| 2104 | const io = step_owner.graph.io; | |
| 2105 | ||
| 2106 | f.batch.cancel(io); | |
| 2107 | gpa.free(f.batch.storage); | |
| 2108 | var total_rss: usize = 0; | |
| 2109 | for (f.instances) |*instance| { | |
| 2110 | instance.child.kill(io); | |
| 2111 | instance.message.deinit(gpa); | |
| 2112 | instance.stderr.deinit(gpa); | |
| 2113 | instance.progress_node.end(); | |
| 2114 | total_rss += instance.child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 2115 | } | |
| 2116 | f.run.step.result_peak_rss = @max(f.run.step.result_peak_rss, total_rss); | |
| 2117 | gpa.free(f.instances); | |
| 2118 | } | |
| 2119 | ||
| 2120 | fn startInstances(f: *FuzzTestRunner) !void { | |
| 2121 | const step_owner = f.run.step.owner; | |
| 2122 | const io = step_owner.graph.io; | |
| 2123 | ||
| 2124 | for (0.., f.instances) |id, *instance| { | |
| 2125 | const id32: u32 = @intCast(id); | |
| 2126 | (switch (f.ctx.fuzz.mode) { | |
| 2127 | .forever => sendRunFuzzTestMessage( | |
| 2128 | io, | |
| 2129 | instance.child.stdin.?, | |
| 2130 | f.run.fuzz_tests.items, | |
| 2131 | .forever, | |
| 2132 | id32, | |
| 2133 | ), | |
| 2134 | .limit => |limit| sendRunFuzzTestMessage( | |
| 2135 | io, | |
| 2136 | instance.child.stdin.?, | |
| 2137 | f.run.fuzz_tests.items, | |
| 2138 | .iterations, | |
| 2139 | limit.amount, | |
| 2140 | ), | |
| 2141 | }) catch |write_err| { | |
| 2142 | // The runner unexpectedly closed stdin, which means it crashed during initialization. | |
| 2143 | // Clean up everything and wait for the child to exit. | |
| 2144 | instance.child.stdin.?.close(io); | |
| 2145 | instance.child.stdin = null; | |
| 2146 | const term = try instance.child.wait(io); | |
| 2147 | return f.run.step.fail( | |
| 2148 | "unable to write stdin ({t}); test process unexpectedly {f}", | |
| 2149 | .{ write_err, fmtTerm(term) }, | |
| 2150 | ); | |
| 2151 | }; | |
| 2152 | ||
| 2153 | try f.addStdoutRead(id32, @sizeOf(InHeader)); | |
| 2154 | try f.addStderrRead(id32); | |
| 2155 | } | |
| 2156 | } | |
| 2157 | ||
| 2158 | fn listen(f: *FuzzTestRunner) !void { | |
| 2159 | const step_owner = f.run.step.owner; | |
| 2160 | const io = step_owner.graph.io; | |
| 2161 | ||
| 2162 | while (true) { | |
| 2163 | try f.batch.awaitConcurrent(io, .none); | |
| 2164 | while (f.batch.next()) |completion| { | |
| 2165 | const id = completion.index / 3; | |
| 2166 | const result = completion.result; | |
| 2167 | switch (completion.index % 3) { | |
| 2168 | 0 => try f.completeStdinWrite(id, result.file_write_streaming catch |e| switch (e) { | |
| 2169 | // Avoid calling `instanceEos` until EndOfStream is seen with stderr so | |
| 2170 | // that all stderr is collected. | |
| 2171 | error.BrokenPipe => continue, | |
| 2172 | else => |write_e| return write_e, | |
| 2173 | }), | |
| 2174 | 1 => try f.completeStdoutRead(id, result.file_read_streaming catch |e| switch (e) { | |
| 2175 | // Avoid calling `instanceEos` until EndOfStream is seen with stderr so | |
| 2176 | // that all stderr is collected. | |
| 2177 | error.EndOfStream => continue, | |
| 2178 | else => |read_e| return read_e, | |
| 2179 | }), | |
| 2180 | 2 => try f.completeStderrRead(id, result.file_read_streaming catch |e| switch (e) { | |
| 2181 | error.EndOfStream => return f.instanceEos(id), | |
| 2182 | else => |read_e| return read_e, | |
| 2183 | }), | |
| 2184 | else => unreachable, | |
| 2185 | } | |
| 2186 | } | |
| 2187 | } | |
| 2188 | } | |
| 2189 | ||
| 2190 | fn completeStdoutRead(f: *FuzzTestRunner, id: u32, n: usize) !void { | |
| 2191 | const step_owner = f.run.step.owner; | |
| 2192 | const gpa = step_owner.allocator; | |
| 2193 | const io = step_owner.graph.io; | |
| 2194 | const instance = &f.instances[id]; | |
| 2195 | ||
| 2196 | instance.message.items.len += n; | |
| 2197 | const total_read = instance.message.items.len; | |
| 2198 | if (total_read < @sizeOf(InHeader)) { | |
| 2199 | try f.addStdoutRead(id, @sizeOf(InHeader)); | |
| 2200 | return; | |
| 2201 | } | |
| 2202 | ||
| 2203 | const header = instance.messageHeader(); | |
| 2204 | const body = instance.message.items[@sizeOf(InHeader)..]; | |
| 2205 | if (body.len != header.bytes_len) { | |
| 2206 | try f.addStdoutRead(id, @sizeOf(InHeader) + header.bytes_len); | |
| 2207 | return; | |
| 2208 | } | |
| 2209 | ||
| 2210 | switch (header.tag) { | |
| 2211 | .zig_version => { | |
| 2212 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) return f.run.step.fail( | |
| 2213 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | |
| 2214 | .{ builtin.zig_version_string, body }, | |
| 2215 | ); | |
| 2216 | }, | |
| 2217 | .coverage_id => { | |
| 2218 | var body_r: Io.Reader = .fixed(body); | |
| 2219 | f.coverage_id = body_r.takeInt(u64, .little) catch unreachable; | |
| 2220 | const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable; | |
| 2221 | const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable; | |
| 2222 | const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable; | |
| 2223 | ||
| 2224 | const fuzz = f.ctx.fuzz; | |
| 2225 | fuzz.queue_mutex.lockUncancelable(io); | |
| 2226 | defer fuzz.queue_mutex.unlock(io); | |
| 2227 | try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{ | |
| 2228 | .id = f.coverage_id.?, | |
| 2229 | .cumulative = .{ | |
| 2230 | .runs = cumulative_runs, | |
| 2231 | .unique = cumulative_unique, | |
| 2232 | .coverage = cumulative_coverage, | |
| 2233 | }, | |
| 2234 | .run = f.run, | |
| 2235 | } }); | |
| 2236 | fuzz.queue_cond.signal(io); | |
| 2237 | }, | |
| 2238 | .fuzz_start_addr => { | |
| 2239 | var body_r: Io.Reader = .fixed(body); | |
| 2240 | const fuzz = f.ctx.fuzz; | |
| 2241 | const addr = body_r.takeInt(u64, .little) catch unreachable; | |
| 2242 | ||
| 2243 | fuzz.queue_mutex.lockUncancelable(io); | |
| 2244 | defer fuzz.queue_mutex.unlock(io); | |
| 2245 | try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{ | |
| 2246 | .addr = addr, | |
| 2247 | .coverage_id = f.coverage_id.?, | |
| 2248 | } }); | |
| 2249 | fuzz.queue_cond.signal(io); | |
| 2250 | }, | |
| 2251 | .fuzz_test_change => { | |
| 2252 | const test_i = std.mem.readInt(u32, body[0..4], .little); | |
| 2253 | instance.progress_node.setName(f.run.fuzz_tests.items[test_i]); | |
| 2254 | }, | |
| 2255 | .broadcast_fuzz_input => { | |
| 2256 | if (f.instances.len == 1) { | |
| 2257 | // No other processes to broadcast to. | |
| 2258 | } else if (f.broadcast_undelivered == 0) { | |
| 2259 | try f.instanceBroadcast(id, body); | |
| 2260 | } else { | |
| 2261 | const footer: PendingBroadcastFooter = .{ | |
| 2262 | .from_id = id, | |
| 2263 | .body_len = @intCast(body.len), | |
| 2264 | }; | |
| 2265 | // There is another broadcast in progress so add this one to the queue. | |
| 2266 | const size = @sizeOf(PendingBroadcastFooter) + body.len; | |
| 2267 | try f.pending_broadcasts.ensureUnusedCapacity(gpa, size); | |
| 2268 | f.pending_broadcasts.appendSliceAssumeCapacity(body); | |
| 2269 | f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer)); | |
| 2270 | } | |
| 2271 | }, | |
| 2272 | else => {}, // ignore other messages | |
| 2273 | } | |
| 2274 | ||
| 2275 | instance.message.clearRetainingCapacity(); | |
| 2276 | try f.addStdoutRead(id, @sizeOf(InHeader)); | |
| 2277 | } | |
| 2278 | ||
| 2279 | fn completeStderrRead(f: *FuzzTestRunner, id: u32, n: usize) !void { | |
| 2280 | const instance = &f.instances[id]; | |
| 2281 | instance.stderr.items.len += n; | |
| 2282 | try f.addStderrRead(id); | |
| 2283 | } | |
| 2284 | ||
| 2285 | fn completeStdinWrite(f: *FuzzTestRunner, id: u32, n: usize) !void { | |
| 2286 | const instance = &f.instances[id]; | |
| 2287 | ||
| 2288 | instance.broadcast_written += n; | |
| 2289 | if (instance.broadcast_written == f.broadcast.items.len) { | |
| 2290 | f.broadcast_undelivered -= 1; | |
| 2291 | if (f.broadcast_undelivered == 0) { | |
| 2292 | try f.broadcastComplete(); | |
| 2293 | } | |
| 2294 | } else { | |
| 2295 | f.addStdinWrite(id); | |
| 2296 | } | |
| 2297 | } | |
| 2298 | ||
| 2299 | fn addStdoutRead(f: *FuzzTestRunner, id: u32, end: usize) !void { | |
| 2300 | const step_owner = f.run.step.owner; | |
| 2301 | const gpa = step_owner.allocator; | |
| 2302 | const instance = &f.instances[id]; | |
| 2303 | ||
| 2304 | try instance.message.ensureTotalCapacity(gpa, end); | |
| 2305 | const start = instance.message.items.len; | |
| 2306 | instance.stdout_vec = .{instance.message.allocatedSlice()[start..end]}; | |
| 2307 | f.batch.addAt(id * 3 + stdout_i, .{ .file_read_streaming = .{ | |
| 2308 | .file = instance.child.stdout.?, | |
| 2309 | .data = &instance.stdout_vec, | |
| 2310 | } }); | |
| 2311 | } | |
| 2312 | ||
| 2313 | fn addStderrRead(f: *FuzzTestRunner, id: u32) !void { | |
| 2314 | const step_owner = f.run.step.owner; | |
| 2315 | const gpa = step_owner.allocator; | |
| 2316 | const instance = &f.instances[id]; | |
| 2317 | ||
| 2318 | try instance.stderr.ensureUnusedCapacity(gpa, 1); | |
| 2319 | instance.stderr_vec = .{instance.stderr.unusedCapacitySlice()}; | |
| 2320 | f.batch.addAt(id * 3 + stderr_i, .{ .file_read_streaming = .{ | |
| 2321 | .file = instance.child.stderr.?, | |
| 2322 | .data = &instance.stderr_vec, | |
| 2323 | } }); | |
| 2324 | } | |
| 2325 | ||
| 2326 | fn addStdinWrite(f: *FuzzTestRunner, id: u32) void { | |
| 2327 | const instance = &f.instances[id]; | |
| 2328 | ||
| 2329 | assert(f.broadcast.items.len != instance.broadcast_written); | |
| 2330 | instance.stdin_vec = .{f.broadcast.items[instance.broadcast_written..]}; | |
| 2331 | f.batch.addAt(id * 3 + stdin_i, .{ .file_write_streaming = .{ | |
| 2332 | .file = instance.child.stdin.?, | |
| 2333 | .data = &instance.stdin_vec, | |
| 2334 | } }); | |
| 2335 | } | |
| 2336 | ||
| 2337 | fn instanceEos(f: *FuzzTestRunner, id: u32) !void { | |
| 2338 | const step_owner = f.run.step.owner; | |
| 2339 | const io = step_owner.graph.io; | |
| 2340 | const instance = &f.instances[id]; | |
| 2341 | ||
| 2342 | instance.child.stdin.?.close(io); | |
| 2343 | instance.child.stdin = null; | |
| 2344 | const term = try instance.child.wait(io); | |
| 2345 | if (!termMatches(.{ .exited = 0 }, term)) { | |
| 2346 | f.run.step.result_stderr = try f.mergedStderr(); | |
| 2347 | try f.saveCrash(id, term); | |
| 2348 | return f.run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)}); | |
| 2349 | } | |
| 2350 | } | |
| 2351 | ||
| 2352 | fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void { | |
| 2353 | const step = &f.run.step; | |
| 2354 | const b = step.owner; | |
| 2355 | const io = b.graph.io; | |
| 2356 | ||
| 2357 | if (f.coverage_id == null) return; | |
| 2358 | ||
| 2359 | // Search for the input file corresponding to the instance | |
| 2360 | const InputHeader = Build.abi.fuzz.MmapInputHeader; | |
| 2361 | var in_r_buf: [@sizeOf(InputHeader)]u8 = undefined; | |
| 2362 | var in_r: Io.File.Reader = undefined; | |
| 2363 | var in_f: Io.File = undefined; | |
| 2364 | var in_name_buf: [12]u8 = undefined; | |
| 2365 | var in_name: []const u8 = undefined; | |
| 2366 | var i: u32 = 0; | |
| 2367 | const header: InputHeader = while (true) : ({ | |
| 2368 | if (i == std.math.maxInt(u32)) return; | |
| 2369 | i += 1; | |
| 2370 | }) { | |
| 2371 | const name_prefix = "f" ++ Io.Dir.path.sep_str ++ "in"; | |
| 2372 | in_name = std.fmt.bufPrint(&in_name_buf, name_prefix ++ "{x}", .{i}) catch unreachable; | |
| 2373 | in_f = b.cache_root.handle.openFile(io, in_name, .{ | |
| 2374 | .lock = .exclusive, | |
| 2375 | .lock_nonblocking = true, | |
| 2376 | }) catch |e| switch (e) { | |
| 2377 | error.FileNotFound => return, | |
| 2378 | error.WouldBlock => continue, // Can not be from | |
| 2379 | // the crashed instance since it is still locked. | |
| 2380 | else => return step.fail("failed to open file '{f}{s}': {t}", .{ | |
| 2381 | b.cache_root, in_name, e, | |
| 2382 | }), | |
| 2383 | }; | |
| 2384 | ||
| 2385 | in_r = in_f.readerStreaming(io, &in_r_buf); | |
| 2386 | const header = in_r.interface.takeStruct(InputHeader, .little) catch |e| { | |
| 2387 | in_f.close(io); | |
| 2388 | switch (e) { | |
| 2389 | error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{ | |
| 2390 | b.cache_root, in_name, in_r.err.?, | |
| 2391 | }), | |
| 2392 | error.EndOfStream => continue, | |
| 2393 | } | |
| 2394 | }; | |
| 2395 | ||
| 2396 | if (header.pc_digest == f.coverage_id.? and | |
| 2397 | header.instance_id == id and | |
| 2398 | header.test_i < f.run.fuzz_tests.items.len) | |
| 2399 | { | |
| 2400 | break header; | |
| 2401 | } | |
| 2402 | ||
| 2403 | in_f.close(io); | |
| 2404 | }; | |
| 2405 | defer in_f.close(io); | |
| 2406 | ||
| 2407 | // Save it to a seperate file | |
| 2408 | const crash_name = "f" ++ Io.Dir.path.sep_str ++ "crash"; | |
| 2409 | const out = b.cache_root.handle.createFile(io, crash_name, .{ | |
| 2410 | .lock = .exclusive, // Multiple run steps could have found a crash at the same time | |
| 2411 | }) catch |e| return step.fail("failed to create file '{f}{s}': {t}", .{ | |
| 2412 | b.cache_root, crash_name, e, | |
| 2413 | }); | |
| 2414 | defer out.close(io); | |
| 2415 | ||
| 2416 | var out_w_buf: [512]u8 = undefined; | |
| 2417 | var out_w = out.writerStreaming(io, &out_w_buf); | |
| 2418 | _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) { | |
| 2419 | error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{ | |
| 2420 | b.cache_root, in_name, in_r.err.?, | |
| 2421 | }), | |
| 2422 | error.WriteFailed => return step.fail("failed to write file '{f}{s}': {t}", .{ | |
| 2423 | b.cache_root, crash_name, out_w.err.?, | |
| 2424 | }), | |
| 2425 | }; | |
| 2426 | ||
| 2427 | return f.run.step.fail("test '{s}' {f}; input saved to '{f}{s}'", .{ | |
| 2428 | f.run.fuzz_tests.items[header.test_i], | |
| 2429 | fmtTerm(term), | |
| 2430 | b.cache_root, | |
| 2431 | crash_name, | |
| 2432 | }); | |
| 2433 | } | |
| 2434 | ||
| 2435 | fn instanceBroadcast(f: *FuzzTestRunner, from_id: u32, bytes: []const u8) !void { | |
| 2436 | assert(f.instances.len > 1); | |
| 2437 | assert(f.broadcast_undelivered == 0); // no other broadcast is progress | |
| 2438 | assert(f.broadcast.items.len == 0); | |
| 2439 | assert(from_id < f.instances.len); | |
| 2440 | ||
| 2441 | const step_owner = f.run.step.owner; | |
| 2442 | const gpa = step_owner.allocator; | |
| 2443 | ||
| 2444 | var out_header: OutHeader = .{ | |
| 2445 | .tag = .new_fuzz_input, | |
| 2446 | .bytes_len = @intCast(bytes.len), | |
| 2447 | }; | |
| 2448 | if (std.builtin.Endian.native != .little) { | |
| 2449 | std.mem.byteSwapAllFields(OutHeader, &out_header); | |
| 2450 | } | |
| 2451 | try f.broadcast.ensureTotalCapacity(gpa, @sizeOf(OutHeader) + bytes.len); | |
| 2452 | f.broadcast.appendSliceAssumeCapacity(@ptrCast(&out_header)); | |
| 2453 | f.broadcast.appendSliceAssumeCapacity(bytes); | |
| 2454 | ||
| 2455 | f.broadcast_undelivered = @intCast(f.instances.len - 1); | |
| 2456 | for (0.., f.instances) |to_id, *instance| { | |
| 2457 | if (to_id == from_id) continue; | |
| 2458 | instance.broadcast_written = 0; | |
| 2459 | f.addStdinWrite(@intCast(to_id)); | |
| 2460 | } | |
| 2461 | } | |
| 2462 | ||
| 2463 | fn broadcastComplete(f: *FuzzTestRunner) !void { | |
| 2464 | assert(f.instances.len > 1); | |
| 2465 | assert(f.broadcast_undelivered == 0); | |
| 2466 | f.broadcast.clearRetainingCapacity(); | |
| 2467 | ||
| 2468 | const pending = &f.pending_broadcasts; | |
| 2469 | if (pending.items.len != 0) { | |
| 2470 | // Another broadcast is pending; copy it over to `broadcast` | |
| 2471 | ||
| 2472 | const footer_len = @sizeOf(PendingBroadcastFooter); | |
| 2473 | const footer_bytes = pending.items[pending.items.len - footer_len ..]; | |
| 2474 | const footer: *align(1) PendingBroadcastFooter = @ptrCast(footer_bytes); | |
| 2475 | pending.items.len -= footer_len; | |
| 2476 | ||
| 2477 | const body = pending.items[pending.items.len - footer.body_len ..]; | |
| 2478 | try f.instanceBroadcast(footer.from_id, body); | |
| 2479 | pending.items.len -= body.len; | |
| 2480 | } | |
| 2481 | } | |
| 2482 | ||
| 2483 | fn mergedStderr(f: *FuzzTestRunner) std.mem.Allocator.Error![]const u8 { | |
| 2484 | const step_owner = f.run.step.owner; | |
| 2485 | const arena = step_owner.allocator; | |
| 2486 | ||
| 2487 | // Collect any available stderr | |
| 2488 | while (f.batch.next()) |completion| { | |
| 2489 | if (completion.index % 3 != 2) continue; | |
| 2490 | const len = completion.result.file_read_streaming catch continue; | |
| 2491 | f.instances[completion.index / 3].stderr.items.len += len; | |
| 2492 | } | |
| 2493 | ||
| 2494 | var stderr_len: usize = 0; | |
| 2495 | for (f.instances) |*instance| stderr_len += instance.stderr.items.len; | |
| 2496 | const stderr = try arena.alloc(u8, stderr_len); | |
| 2497 | ||
| 2498 | stderr_len = 0; | |
| 2499 | for (f.instances) |*instance| { | |
| 2500 | @memcpy(stderr[stderr_len..][0..instance.stderr.items.len], instance.stderr.items); | |
| 2501 | stderr_len += instance.stderr.items.len; | |
| 2502 | } | |
| 2503 | return stderr; | |
| 2504 | } | |
| 2505 | }; | |
| 2506 | ||
| 2507 | fn evalFuzzTest( | |
| 2508 | run: *Run, | |
| 2509 | spawn_options: process.SpawnOptions, | |
| 2510 | options: Step.MakeOptions, | |
| 2511 | fuzz_context: FuzzContext, | |
| 2512 | ) !void { | |
| 2513 | var f: FuzzTestRunner = try .init(run, fuzz_context, options.progress_node, spawn_options); | |
| 2514 | defer f.deinit(); | |
| 2515 | try f.startInstances(); | |
| 2516 | try f.listen(); | |
| 2517 | } | |
| 2518 | ||
| 2519 | const TestMetadata = struct { | |
| 2520 | names: []const u32, | |
| 2521 | ns_per_test: []u64, | |
| 2522 | expected_panic_msgs: []const u32, | |
| 2523 | string_bytes: []const u8, | |
| 2524 | next_index: u32, | |
| 2525 | prog_node: std.Progress.Node, | |
| 2526 | ||
| 2527 | fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata { | |
| 2528 | return .{ | |
| 2529 | .names = tm.names, | |
| 2530 | .string_bytes = tm.string_bytes, | |
| 2531 | }; | |
| 2532 | } | |
| 2533 | ||
| 2534 | fn testName(tm: TestMetadata, index: u32) []const u8 { | |
| 2535 | return tm.toCachedTestMetadata().testName(index); | |
| 2536 | } | |
| 2537 | }; | |
| 2538 | ||
| 2539 | pub const CachedTestMetadata = struct { | |
| 2540 | names: []const u32, | |
| 2541 | string_bytes: []const u8, | |
| 2542 | ||
| 2543 | pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 { | |
| 2544 | return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); | |
| 2545 | } | |
| 2546 | }; | |
| 2547 | ||
| 2548 | fn requestNextTest(io: Io, in: Io.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { | |
| 2549 | while (metadata.next_index < metadata.names.len) { | |
| 2550 | const i = metadata.next_index; | |
| 2551 | metadata.next_index += 1; | |
| 2552 | ||
| 2553 | if (metadata.expected_panic_msgs[i] != 0) continue; | |
| 2554 | ||
| 2555 | const name = metadata.testName(i); | |
| 2556 | if (sub_prog_node.*) |n| n.end(); | |
| 2557 | sub_prog_node.* = metadata.prog_node.start(name, 0); | |
| 2558 | ||
| 2559 | try sendRunTestMessage(io, in, .run_test, i); | |
| 2560 | return; | |
| 2561 | } else { | |
| 2562 | metadata.next_index = std.math.maxInt(u32); // indicate that all tests are done | |
| 2563 | try sendMessage(io, in, .exit); | |
| 2564 | } | |
| 2565 | } | |
| 2566 | ||
| 2567 | fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { | |
| 2568 | const header: std.zig.Client.Message.Header = .{ | |
| 2569 | .tag = tag, | |
| 2570 | .bytes_len = 0, | |
| 2571 | }; | |
| 2572 | var w = file.writerStreaming(io, &.{}); | |
| 2573 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 2574 | error.WriteFailed => return w.err.?, | |
| 2575 | }; | |
| 2576 | } | |
| 2577 | ||
| 2578 | fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, index: u32) !void { | |
| 2579 | const header: std.zig.Client.Message.Header = .{ | |
| 2580 | .tag = tag, | |
| 2581 | .bytes_len = 4, | |
| 2582 | }; | |
| 2583 | var w = file.writerStreaming(io, &.{}); | |
| 2584 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 2585 | error.WriteFailed => return w.err.?, | |
| 2586 | }; | |
| 2587 | w.interface.writeInt(u32, index, .little) catch |err| switch (err) { | |
| 2588 | error.WriteFailed => return w.err.?, | |
| 2589 | }; | |
| 2590 | } | |
| 2591 | ||
| 2592 | fn sendRunFuzzTestMessage( | |
| 2593 | io: Io, | |
| 2594 | file: Io.File, | |
| 2595 | test_names: []const []const u8, | |
| 2596 | kind: std.Build.abi.fuzz.LimitKind, | |
| 2597 | amount_or_instance: u64, | |
| 2598 | ) !void { | |
| 2599 | const header: std.zig.Client.Message.Header = .{ | |
| 2600 | .tag = .start_fuzzing, | |
| 2601 | .bytes_len = 1 + 8 + 4 + count: { | |
| 2602 | var c: u32 = @intCast(test_names.len * 4); | |
| 2603 | for (test_names) |name| { | |
| 2604 | c += @intCast(name.len); | |
| 2605 | } | |
| 2606 | break :count c; | |
| 2607 | }, | |
| 2608 | }; | |
| 2609 | var w = file.writerStreaming(io, &.{}); | |
| 2610 | w.interface.writeStruct(header, .little) catch |err| switch (err) { | |
| 2611 | error.WriteFailed => return w.err.?, | |
| 2612 | }; | |
| 2613 | w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) { | |
| 2614 | error.WriteFailed => return w.err.?, | |
| 2615 | }; | |
| 2616 | w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) { | |
| 2617 | error.WriteFailed => return w.err.?, | |
| 2618 | }; | |
| 2619 | w.interface.writeInt(u32, @intCast(test_names.len), .little) catch |err| switch (err) { | |
| 2620 | error.WriteFailed => return w.err.?, | |
| 2621 | }; | |
| 2622 | for (test_names) |test_name| { | |
| 2623 | w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) { | |
| 2624 | error.WriteFailed => return w.err.?, | |
| 2625 | }; | |
| 2626 | w.interface.writeAll(test_name) catch |err| switch (err) { | |
| 2627 | error.WriteFailed => return w.err.?, | |
| 2628 | }; | |
| 2629 | } | |
| 2630 | } | |
| 2631 | ||
| 2632 | fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult { | |
| 2633 | const b = run.step.owner; | |
| 2634 | const io = b.graph.io; | |
| 2635 | const arena = b.allocator; | |
| 2636 | const gpa = b.allocator; | |
| 2637 | ||
| 2638 | var child = try process.spawn(io, spawn_options); | |
| 2639 | defer child.kill(io); | |
| 2640 | ||
| 2641 | switch (run.stdin) { | |
| 2642 | .bytes => |bytes| { | |
| 2643 | child.stdin.?.writeStreamingAll(io, bytes) catch |err| { | |
| 2644 | return run.step.fail("unable to write stdin: {t}", .{err}); | |
| 2645 | }; | |
| 2646 | child.stdin.?.close(io); | |
| 2647 | child.stdin = null; | |
| 2648 | }, | |
| 2649 | .lazy_path => |lazy_path| { | |
| 2650 | const path = lazy_path.getPath3(b, &run.step); | |
| 2651 | const file = path.root_dir.handle.openFile(io, path.subPathOrDot(), .{}) catch |err| { | |
| 2652 | return run.step.fail("unable to open stdin file: {t}", .{err}); | |
| 2653 | }; | |
| 2654 | defer file.close(io); | |
| 2655 | // TODO https://github.com/ziglang/zig/issues/23955 | |
| 2656 | var read_buffer: [1024]u8 = undefined; | |
| 2657 | var file_reader = file.reader(io, &read_buffer); | |
| 2658 | var write_buffer: [1024]u8 = undefined; | |
| 2659 | var stdin_writer = child.stdin.?.writerStreaming(io, &write_buffer); | |
| 2660 | _ = stdin_writer.interface.sendFileAll(&file_reader, .unlimited) catch |err| switch (err) { | |
| 2661 | error.ReadFailed => return run.step.fail("failed to read from {f}: {t}", .{ | |
| 2662 | path, file_reader.err.?, | |
| 2663 | }), | |
| 2664 | error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{ | |
| 2665 | stdin_writer.err.?, | |
| 2666 | }), | |
| 2667 | }; | |
| 2668 | stdin_writer.interface.flush() catch |err| switch (err) { | |
| 2669 | error.WriteFailed => return run.step.fail("failed to write to stdin: {t}", .{ | |
| 2670 | stdin_writer.err.?, | |
| 2671 | }), | |
| 2672 | }; | |
| 2673 | child.stdin.?.close(io); | |
| 2674 | child.stdin = null; | |
| 2675 | }, | |
| 2676 | .none => {}, | |
| 2677 | } | |
| 2678 | ||
| 2679 | var stdout_bytes: ?[]const u8 = null; | |
| 2680 | var stderr_bytes: ?[]const u8 = null; | |
| 2681 | ||
| 2682 | if (child.stdout) |stdout| { | |
| 2683 | if (child.stderr) |stderr| { | |
| 2684 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 2685 | var multi_reader: Io.File.MultiReader = undefined; | |
| 2686 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr }); | |
| 2687 | defer multi_reader.deinit(); | |
| 2688 | ||
| 2689 | const stdout_reader = multi_reader.reader(0); | |
| 2690 | const stderr_reader = multi_reader.reader(1); | |
| 2691 | ||
| 2692 | while (multi_reader.fill(64, .none)) |_| { | |
| 2693 | if (run.stdio_limit.toInt()) |limit| { | |
| 2694 | if (stdout_reader.buffered().len > limit) | |
| 2695 | return error.StdoutStreamTooLong; | |
| 2696 | if (stderr_reader.buffered().len > limit) | |
| 2697 | return error.StderrStreamTooLong; | |
| 2698 | } | |
| 2699 | } else |err| switch (err) { | |
| 2700 | error.Timeout => unreachable, | |
| 2701 | error.EndOfStream => {}, | |
| 2702 | else => |e| return e, | |
| 2703 | } | |
| 2704 | ||
| 2705 | try multi_reader.checkAnyError(); | |
| 2706 | ||
| 2707 | // TODO: this string can leak since alloc below can return error. | |
| 2708 | stdout_bytes = try multi_reader.toOwnedSlice(0); | |
| 2709 | // TODO: this string can leak since its allocated using gpa and `try child.wait(io)` below can fail. | |
| 2710 | stderr_bytes = try multi_reader.toOwnedSlice(1); | |
| 2711 | } else { | |
| 2712 | var stdout_reader = stdout.readerStreaming(io, &.{}); | |
| 2713 | stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) { | |
| 2714 | error.OutOfMemory => |e| return e, | |
| 2715 | error.ReadFailed => return stdout_reader.err.?, | |
| 2716 | error.StreamTooLong => return error.StdoutStreamTooLong, | |
| 2717 | }; | |
| 2718 | } | |
| 2719 | } else if (child.stderr) |stderr| { | |
| 2720 | var stderr_reader = stderr.readerStreaming(io, &.{}); | |
| 2721 | stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) { | |
| 2722 | error.OutOfMemory => |e| return e, | |
| 2723 | error.ReadFailed => return stderr_reader.err.?, | |
| 2724 | error.StreamTooLong => return error.StderrStreamTooLong, | |
| 2725 | }; | |
| 2726 | } | |
| 2727 | ||
| 2728 | if (stderr_bytes) |bytes| if (bytes.len > 0) { | |
| 2729 | // Treat stderr as an error message. | |
| 2730 | const stderr_is_diagnostic = run.captured_stderr == null and switch (run.stdio) { | |
| 2731 | .check => |checks| !checksContainStderr(checks.items), | |
| 2732 | else => true, | |
| 2733 | }; | |
| 2734 | if (stderr_is_diagnostic) { | |
| 2735 | run.step.result_stderr = bytes; | |
| 2736 | } | |
| 2737 | }; | |
| 2738 | ||
| 2739 | run.step.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0; | |
| 2740 | ||
| 2741 | return .{ | |
| 2742 | .term = try child.wait(io), | |
| 2743 | .stdout = stdout_bytes, | |
| 2744 | .stderr = stderr_bytes, | |
| 2745 | }; | |
| 2746 | } | |
| 2747 | ||
| 2748 | fn addPathForDynLibs(run: *Run, artifact: *Step.Compile) void { | |
| 2749 | const b = run.step.owner; | |
| 2750 | const compiles = artifact.getCompileDependencies(true); | |
| 2751 | for (compiles) |compile| { | |
| 2752 | if (compile.root_module.resolved_target.?.result.os.tag == .windows and | |
| 2753 | compile.isDynamicLibrary()) | |
| 2754 | { | |
| 2755 | addPathDir(run, Dir.path.dirname(compile.getEmittedBin().getPath2(b, &run.step)).?); | |
| 2756 | } | |
| 2757 | } | |
| 2758 | } | |
| 2759 | ||
| 2760 | fn failForeign( | |
| 2761 | run: *Run, | |
| 2762 | suggested_flag: []const u8, | |
| 2763 | argv0: []const u8, | |
| 2764 | exe: *Step.Compile, | |
| 2765 | ) error{ MakeFailed, MakeSkipped, OutOfMemory } { | |
| 2766 | switch (run.stdio) { | |
| 2767 | .check, .zig_test => { | |
| 2768 | if (run.skip_foreign_checks) | |
| 2769 | return error.MakeSkipped; | |
| 2770 | ||
| 2771 | const b = run.step.owner; | |
| 2772 | const host_name = try b.graph.host.result.zigTriple(b.allocator); | |
| 2773 | const foreign_name = try exe.rootModuleTarget().zigTriple(b.allocator); | |
| 2774 | ||
| 2775 | return run.step.fail( | |
| 2776 | \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) | |
| 2777 | \\ consider using {s} or enabling skip_foreign_checks in the Run step | |
| 2778 | , .{ argv0, foreign_name, host_name, suggested_flag }); | |
| 2779 | }, | |
| 2780 | else => { | |
| 2781 | return run.step.fail("unable to spawn foreign binary '{s}'", .{argv0}); | |
| 2782 | }, | |
| 2783 | } | |
| 2784 | } | |
| 2785 | ||
| 2786 | fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { | |
| 2787 | switch (stdio) { | |
| 2788 | .infer_from_args, .inherit, .zig_test => {}, | |
| 2789 | .check => |checks| for (checks.items) |check| { | |
| 2790 | hh.add(@as(std.meta.Tag(StdIo.Check), check)); | |
| 2791 | switch (check) { | |
| 2792 | .expect_stderr_exact, | |
| 2793 | .expect_stderr_match, | |
| 2794 | .expect_stdout_exact, | |
| 2795 | .expect_stdout_match, | |
| 2796 | => |s| hh.addBytes(s), | |
| 2797 | ||
| 2798 | .expect_term => |term| { | |
| 2799 | hh.add(@as(std.meta.Tag(process.Child.Term), term)); | |
| 2800 | switch (term) { | |
| 2801 | inline .exited, .signal, .stopped => |x| hh.add(x), | |
| 2802 | .unknown => |x| hh.add(x), | |
| 2803 | } | |
| 2804 | }, | |
| 2805 | } | |
| 2806 | }, | |
| 2807 | } | |
| 724 | file_input.addStepDependencies(&run.step); | |
| 725 | run.file_inputs.append(arena, file_input.dupe(graph)) catch @panic("OOM"); | |
| 2808 | 726 | } |
lib/std/Build/Step/TranslateC.zig+55-164| ... | ... | @@ -1,24 +1,25 @@ |
| 1 | const TranslateC = @This(); | |
| 2 | ||
| 1 | 3 | const std = @import("std"); |
| 2 | const Step = std.Build.Step; | |
| 3 | const LazyPath = std.Build.LazyPath; | |
| 4 | 4 | const fs = std.fs; |
| 5 | 5 | const mem = std.mem; |
| 6 | ||
| 7 | const TranslateC = @This(); | |
| 8 | ||
| 9 | pub const base_id: Step.Id = .translate_c; | |
| 6 | const allocPrint = std.fmt.allocPrint; | |
| 7 | const Step = std.Build.Step; | |
| 8 | const LazyPath = std.Build.LazyPath; | |
| 9 | const Configuration = std.Build.Configuration; | |
| 10 | 10 | |
| 11 | 11 | step: Step, |
| 12 | 12 | source: std.Build.LazyPath, |
| 13 | include_dirs: std.array_list.Managed(std.Build.Module.IncludeDir), | |
| 14 | system_libs: std.ArrayList(std.Build.Module.SystemLib), | |
| 15 | c_macros: std.array_list.Managed([]const u8), | |
| 16 | out_basename: []const u8, | |
| 13 | include_dirs: std.ArrayList(std.Build.Module.IncludeDir) = .empty, | |
| 14 | system_libs: std.ArrayList(std.Build.Module.SystemLib) = .empty, | |
| 15 | c_macros: std.ArrayList(Configuration.String) = .empty, | |
| 17 | 16 | target: std.Build.ResolvedTarget, |
| 18 | 17 | optimize: std.builtin.OptimizeMode, |
| 19 | output_file: std.Build.GeneratedFile, | |
| 18 | output_file: Configuration.GeneratedFileIndex, | |
| 20 | 19 | link_libc: bool, |
| 21 | 20 | |
| 21 | pub const base_tag: Step.Tag = .translate_c; | |
| 22 | ||
| 22 | 23 | pub const Options = struct { |
| 23 | 24 | root_source_file: std.Build.LazyPath, |
| 24 | 25 | target: std.Build.ResolvedTarget, |
| ... | ... | @@ -27,24 +28,20 @@ pub const Options = struct { |
| 27 | 28 | }; |
| 28 | 29 | |
| 29 | 30 | pub fn create(owner: *std.Build, options: Options) *TranslateC { |
| 30 | const translate_c = owner.allocator.create(TranslateC) catch @panic("OOM"); | |
| 31 | const source = options.root_source_file.dupe(owner); | |
| 31 | const graph = owner.graph; | |
| 32 | const translate_c = graph.create(TranslateC); | |
| 33 | const source = options.root_source_file.dupe(graph); | |
| 32 | 34 | translate_c.* = .{ |
| 33 | .step = Step.init(.{ | |
| 34 | .id = base_id, | |
| 35 | .step = .init(.{ | |
| 36 | .tag = base_tag, | |
| 35 | 37 | .name = "translate-c", |
| 36 | 38 | .owner = owner, |
| 37 | .makeFn = make, | |
| 38 | 39 | }), |
| 39 | 40 | .source = source, |
| 40 | .include_dirs = std.array_list.Managed(std.Build.Module.IncludeDir).init(owner.allocator), | |
| 41 | .c_macros = std.array_list.Managed([]const u8).init(owner.allocator), | |
| 42 | .out_basename = undefined, | |
| 43 | 41 | .target = options.target, |
| 44 | 42 | .optimize = options.optimize, |
| 45 | .output_file = .{ .step = &translate_c.step }, | |
| 43 | .output_file = graph.addGeneratedFile(&translate_c.step), | |
| 46 | 44 | .link_libc = options.link_libc, |
| 47 | .system_libs = .empty, | |
| 48 | 45 | }; |
| 49 | 46 | source.addStepDependencies(&translate_c.step); |
| 50 | 47 | return translate_c; |
| ... | ... | @@ -59,7 +56,7 @@ pub const AddExecutableOptions = struct { |
| 59 | 56 | }; |
| 60 | 57 | |
| 61 | 58 | pub fn getOutput(translate_c: *TranslateC) std.Build.LazyPath { |
| 62 | return .{ .generated = .{ .file = &translate_c.output_file } }; | |
| 59 | return .{ .generated = .{ .index = translate_c.output_file } }; | |
| 63 | 60 | } |
| 64 | 61 | |
| 65 | 62 | /// Creates a module from the translated source and adds it to the package's |
| ... | ... | @@ -87,8 +84,8 @@ pub fn createModule(translate_c: *TranslateC) *std.Build.Module { |
| 87 | 84 | } |
| 88 | 85 | |
| 89 | 86 | fn setUpModule(translate_c: *TranslateC, module: *std.Build.Module) *std.Build.Module { |
| 90 | const b = translate_c.step.owner; | |
| 91 | const arena = b.graph.arena; | |
| 87 | const graph = translate_c.step.owner.graph; | |
| 88 | const arena = graph.arena; | |
| 92 | 89 | |
| 93 | 90 | if (translate_c.link_libc) module.link_libc = true; |
| 94 | 91 | |
| ... | ... | @@ -100,42 +97,49 @@ fn setUpModule(translate_c: *TranslateC, module: *std.Build.Module) *std.Build.M |
| 100 | 97 | } |
| 101 | 98 | |
| 102 | 99 | pub fn addAfterIncludePath(translate_c: *TranslateC, lazy_path: LazyPath) void { |
| 103 | const b = translate_c.step.owner; | |
| 104 | translate_c.include_dirs.append(.{ .path_after = lazy_path.dupe(b) }) catch | |
| 100 | const graph = translate_c.step.owner.graph; | |
| 101 | const arena = graph.arena; | |
| 102 | translate_c.include_dirs.append(arena, .{ .path_after = lazy_path.dupe(graph) }) catch | |
| 105 | 103 | @panic("OOM"); |
| 106 | 104 | lazy_path.addStepDependencies(&translate_c.step); |
| 107 | 105 | } |
| 108 | 106 | |
| 109 | 107 | pub fn addSystemIncludePath(translate_c: *TranslateC, lazy_path: LazyPath) void { |
| 110 | const b = translate_c.step.owner; | |
| 111 | translate_c.include_dirs.append(.{ .path_system = lazy_path.dupe(b) }) catch | |
| 108 | const graph = translate_c.step.owner.graph; | |
| 109 | const arena = graph.arena; | |
| 110 | translate_c.include_dirs.append(arena, .{ .path_system = lazy_path.dupe(graph) }) catch | |
| 112 | 111 | @panic("OOM"); |
| 113 | 112 | lazy_path.addStepDependencies(&translate_c.step); |
| 114 | 113 | } |
| 115 | 114 | |
| 116 | 115 | pub fn addIncludePath(translate_c: *TranslateC, lazy_path: LazyPath) void { |
| 117 | const b = translate_c.step.owner; | |
| 118 | translate_c.include_dirs.append(.{ .path = lazy_path.dupe(b) }) catch | |
| 116 | const graph = translate_c.step.owner.graph; | |
| 117 | const arena = graph.arena; | |
| 118 | translate_c.include_dirs.append(arena, .{ .path = lazy_path.dupe(graph) }) catch | |
| 119 | 119 | @panic("OOM"); |
| 120 | 120 | lazy_path.addStepDependencies(&translate_c.step); |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | 123 | pub fn addConfigHeader(translate_c: *TranslateC, config_header: *Step.ConfigHeader) void { |
| 124 | translate_c.include_dirs.append(.{ .config_header_step = config_header }) catch | |
| 124 | const graph = translate_c.step.owner.graph; | |
| 125 | const arena = graph.arena; | |
| 126 | translate_c.include_dirs.append(arena, .{ .config_header_step = config_header }) catch | |
| 125 | 127 | @panic("OOM"); |
| 126 | 128 | translate_c.step.dependOn(&config_header.step); |
| 127 | 129 | } |
| 128 | 130 | |
| 129 | 131 | pub fn addSystemFrameworkPath(translate_c: *TranslateC, directory_path: LazyPath) void { |
| 130 | const b = translate_c.step.owner; | |
| 131 | translate_c.include_dirs.append(.{ .framework_path_system = directory_path.dupe(b) }) catch | |
| 132 | const graph = translate_c.step.owner.graph; | |
| 133 | const arena = graph.arena; | |
| 134 | translate_c.include_dirs.append(arena, .{ .framework_path_system = directory_path.dupe(graph) }) catch | |
| 132 | 135 | @panic("OOM"); |
| 133 | 136 | directory_path.addStepDependencies(&translate_c.step); |
| 134 | 137 | } |
| 135 | 138 | |
| 136 | 139 | pub fn addFrameworkPath(translate_c: *TranslateC, directory_path: LazyPath) void { |
| 137 | const b = translate_c.step.owner; | |
| 138 | translate_c.include_dirs.append(.{ .framework_path = directory_path.dupe(b) }) catch | |
| 140 | const graph = translate_c.step.owner.graph; | |
| 141 | const arena = graph.arena; | |
| 142 | translate_c.include_dirs.append(arena, .{ .framework_path = directory_path.dupe(graph) }) catch | |
| 139 | 143 | @panic("OOM"); |
| 140 | 144 | directory_path.addStepDependencies(&translate_c.step); |
| 141 | 145 | } |
| ... | ... | @@ -151,135 +155,21 @@ pub fn addCheckFile(translate_c: *TranslateC, expected_matches: []const []const |
| 151 | 155 | /// If the value is omitted, it is set to 1. |
| 152 | 156 | /// `name` and `value` need not live longer than the function call. |
| 153 | 157 | pub fn defineCMacro(translate_c: *TranslateC, name: []const u8, value: ?[]const u8) void { |
| 154 | const macro = translate_c.step.owner.fmt("{s}={s}", .{ name, value orelse "1" }); | |
| 155 | translate_c.c_macros.append(macro) catch @panic("OOM"); | |
| 158 | const graph = translate_c.step.owner.graph; | |
| 159 | const arena = graph.arena; | |
| 160 | const wc = &graph.wip_configuration; | |
| 161 | const macro = allocPrint(arena, "{s}={s}", .{ name, value orelse "1" }) catch @panic("OOM"); | |
| 162 | const macro_string = wc.addString(macro) catch @panic("OOM"); | |
| 163 | translate_c.c_macros.append(arena, macro_string) catch @panic("OOM"); | |
| 156 | 164 | } |
| 157 | 165 | |
| 158 | /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. | |
| 166 | /// name_and_value looks like [name]=[value]. | |
| 159 | 167 | pub fn defineCMacroRaw(translate_c: *TranslateC, name_and_value: []const u8) void { |
| 160 | translate_c.c_macros.append(translate_c.step.owner.dupe(name_and_value)) catch @panic("OOM"); | |
| 161 | } | |
| 162 | ||
| 163 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 164 | const prog_node = options.progress_node; | |
| 165 | const b = step.owner; | |
| 166 | const translate_c: *TranslateC = @fieldParentPtr("step", step); | |
| 167 | const arena = b.graph.arena; | |
| 168 | ||
| 169 | var argv_list = std.array_list.Managed([]const u8).init(b.allocator); | |
| 170 | try argv_list.append(b.graph.zig_exe); | |
| 171 | try argv_list.append("translate-c"); | |
| 172 | if (translate_c.link_libc) { | |
| 173 | try argv_list.append("-lc"); | |
| 174 | } | |
| 175 | ||
| 176 | try argv_list.append("--cache-dir"); | |
| 177 | try argv_list.append(b.cache_root.path orelse "."); | |
| 178 | ||
| 179 | try argv_list.append("--global-cache-dir"); | |
| 180 | try argv_list.append(b.graph.global_cache_root.path orelse "."); | |
| 181 | ||
| 182 | if (!translate_c.target.query.isNative()) { | |
| 183 | try argv_list.append("-target"); | |
| 184 | try argv_list.append(try translate_c.target.query.zigTriple(b.allocator)); | |
| 185 | } | |
| 186 | ||
| 187 | switch (translate_c.optimize) { | |
| 188 | .Debug => {}, // Skip since it's the default. | |
| 189 | else => try argv_list.append(b.fmt("-O{s}", .{@tagName(translate_c.optimize)})), | |
| 190 | } | |
| 191 | ||
| 192 | for (translate_c.include_dirs.items) |include_dir| { | |
| 193 | try include_dir.appendZigProcessFlags(b, &argv_list, step); | |
| 194 | } | |
| 195 | ||
| 196 | for (translate_c.c_macros.items) |c_macro| { | |
| 197 | try argv_list.append("-D"); | |
| 198 | try argv_list.append(c_macro); | |
| 199 | } | |
| 200 | ||
| 201 | var prev_search_strategy: std.Build.Module.SystemLib.SearchStrategy = .paths_first; | |
| 202 | var prev_preferred_link_mode: std.builtin.LinkMode = .dynamic; | |
| 203 | ||
| 204 | for (translate_c.system_libs.items) |*system_lib| { | |
| 205 | var seen_system_libs: std.StringHashMapUnmanaged([]const []const u8) = .empty; | |
| 206 | const system_lib_gop = try seen_system_libs.getOrPut(arena, system_lib.name); | |
| 207 | if (system_lib_gop.found_existing) { | |
| 208 | try argv_list.appendSlice(system_lib_gop.value_ptr.*); | |
| 209 | continue; | |
| 210 | } else { | |
| 211 | system_lib_gop.value_ptr.* = &.{}; | |
| 212 | } | |
| 213 | ||
| 214 | if (system_lib.search_strategy != prev_search_strategy or | |
| 215 | system_lib.preferred_link_mode != prev_preferred_link_mode) | |
| 216 | { | |
| 217 | switch (system_lib.search_strategy) { | |
| 218 | .no_fallback => switch (system_lib.preferred_link_mode) { | |
| 219 | .dynamic => try argv_list.append("-search_dylibs_only"), | |
| 220 | .static => try argv_list.append("-search_static_only"), | |
| 221 | }, | |
| 222 | .paths_first => switch (system_lib.preferred_link_mode) { | |
| 223 | .dynamic => try argv_list.append("-search_paths_first"), | |
| 224 | .static => try argv_list.append("-search_paths_first_static"), | |
| 225 | }, | |
| 226 | .mode_first => switch (system_lib.preferred_link_mode) { | |
| 227 | .dynamic => try argv_list.append("-search_dylibs_first"), | |
| 228 | .static => try argv_list.append("-search_static_first"), | |
| 229 | }, | |
| 230 | } | |
| 231 | prev_search_strategy = system_lib.search_strategy; | |
| 232 | prev_preferred_link_mode = system_lib.preferred_link_mode; | |
| 233 | } | |
| 234 | ||
| 235 | const prefix: []const u8 = prefix: { | |
| 236 | if (system_lib.needed) break :prefix "-needed-l"; | |
| 237 | if (system_lib.weak) break :prefix "-weak-l"; | |
| 238 | break :prefix "-l"; | |
| 239 | }; | |
| 240 | switch (system_lib.use_pkg_config) { | |
| 241 | .no => try argv_list.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), | |
| 242 | .yes, .force => { | |
| 243 | if (Step.Compile.runPkgConfig(&translate_c.step, system_lib.name)) |result| { | |
| 244 | try argv_list.appendSlice(result.cflags); | |
| 245 | try argv_list.appendSlice(result.libs); | |
| 246 | try seen_system_libs.put(arena, system_lib.name, result.cflags); | |
| 247 | } else |err| switch (err) { | |
| 248 | error.PkgConfigInvalidOutput, | |
| 249 | error.PkgConfigCrashed, | |
| 250 | error.PkgConfigFailed, | |
| 251 | error.PkgConfigNotInstalled, | |
| 252 | error.PackageNotFound, | |
| 253 | => switch (system_lib.use_pkg_config) { | |
| 254 | .yes => { | |
| 255 | // pkg-config failed, so fall back to linking the library | |
| 256 | // by name directly. | |
| 257 | try argv_list.append(b.fmt("{s}{s}", .{ | |
| 258 | prefix, | |
| 259 | system_lib.name, | |
| 260 | })); | |
| 261 | }, | |
| 262 | .force => { | |
| 263 | std.debug.panic("pkg-config failed for library {s}", .{system_lib.name}); | |
| 264 | }, | |
| 265 | .no => unreachable, | |
| 266 | }, | |
| 267 | ||
| 268 | else => |e| return e, | |
| 269 | } | |
| 270 | }, | |
| 271 | } | |
| 272 | } | |
| 273 | ||
| 274 | const c_source_path = translate_c.source.getPath2(b, step); | |
| 275 | try argv_list.append(c_source_path); | |
| 276 | ||
| 277 | try argv_list.append("--listen=-"); | |
| 278 | const output_dir = try step.evalZigProcess(argv_list.items, prog_node, false, options.web_server, options.gpa); | |
| 279 | ||
| 280 | const basename = std.fs.path.stem(std.fs.path.basename(c_source_path)); | |
| 281 | translate_c.out_basename = b.fmt("{s}.zig", .{basename}); | |
| 282 | translate_c.output_file.path = output_dir.?.joinString(b.allocator, translate_c.out_basename) catch @panic("OOM"); | |
| 168 | const graph = translate_c.step.owner.graph; | |
| 169 | const arena = graph.arena; | |
| 170 | const wc = &graph.wip_configuration; | |
| 171 | const macro_string = wc.addString(name_and_value) catch @panic("OOM"); | |
| 172 | translate_c.c_macros.append(arena, macro_string) catch @panic("OOM"); | |
| 283 | 173 | } |
| 284 | 174 | |
| 285 | 175 | pub fn linkSystemLibrary( |
| ... | ... | @@ -287,9 +177,10 @@ pub fn linkSystemLibrary( |
| 287 | 177 | name: []const u8, |
| 288 | 178 | options: std.Build.Module.LinkSystemLibraryOptions, |
| 289 | 179 | ) void { |
| 290 | const b = translate_c.step.owner; | |
| 291 | translate_c.system_libs.append(b.allocator, .{ | |
| 292 | .name = b.dupe(name), | |
| 180 | const graph = translate_c.step.owner.graph; | |
| 181 | const arena = graph.arena; | |
| 182 | translate_c.system_libs.append(arena, .{ | |
| 183 | .name = graph.dupeString(name), | |
| 293 | 184 | .needed = options.needed, |
| 294 | 185 | .weak = options.weak, |
| 295 | 186 | .use_pkg_config = options.use_pkg_config, |
lib/std/Build/Step/UpdateSourceFiles.zig+38-91| ... | ... | @@ -1,116 +1,63 @@ |
| 1 | //! Writes data to paths relative to the package root, effectively mutating the | |
| 2 | //! package's source files. Be careful with the latter functionality; it should | |
| 3 | //! not be used during the normal build process, but as a utility run by a | |
| 4 | //! developer with intention to update source files, which will then be | |
| 5 | //! committed to version control. | |
| 6 | 1 | const UpdateSourceFiles = @This(); |
| 7 | 2 | |
| 8 | 3 | const std = @import("std"); |
| 9 | const Io = std.Io; | |
| 10 | 4 | const Step = std.Build.Step; |
| 11 | const fs = std.fs; | |
| 12 | const ArrayList = std.ArrayList; | |
| 5 | const Configuration = std.Build.Configuration; | |
| 13 | 6 | |
| 14 | 7 | step: Step, |
| 15 | output_source_files: std.ArrayList(OutputSourceFile), | |
| 8 | embeds: std.ArrayList(Embed) = .empty, | |
| 9 | copies: std.ArrayList(Copy) = .empty, | |
| 16 | 10 | |
| 17 | pub const base_id: Step.Id = .update_source_files; | |
| 11 | pub const base_tag: Step.Tag = .update_source_files; | |
| 18 | 12 | |
| 19 | pub const OutputSourceFile = struct { | |
| 20 | contents: Contents, | |
| 21 | sub_path: []const u8, | |
| 22 | }; | |
| 23 | ||
| 24 | pub const Contents = union(enum) { | |
| 25 | bytes: []const u8, | |
| 26 | copy: std.Build.LazyPath, | |
| 27 | }; | |
| 13 | pub const Embed = Step.WriteFile.Embed; | |
| 14 | pub const Copy = Step.WriteFile.Copy; | |
| 28 | 15 | |
| 29 | 16 | pub fn create(owner: *std.Build) *UpdateSourceFiles { |
| 30 | const usf = owner.allocator.create(UpdateSourceFiles) catch @panic("OOM"); | |
| 17 | const graph = owner.graph; | |
| 18 | const usf = graph.create(UpdateSourceFiles); | |
| 31 | 19 | usf.* = .{ |
| 32 | .step = Step.init(.{ | |
| 33 | .id = base_id, | |
| 20 | .step = .init(.{ | |
| 21 | .tag = base_tag, | |
| 34 | 22 | .name = "UpdateSourceFiles", |
| 35 | 23 | .owner = owner, |
| 36 | .makeFn = make, | |
| 37 | 24 | }), |
| 38 | .output_source_files = .empty, | |
| 39 | 25 | }; |
| 40 | 26 | return usf; |
| 41 | 27 | } |
| 42 | 28 | |
| 43 | /// A path relative to the package root. | |
| 29 | /// Overwrites a path relative to the build root with the contents of another file. | |
| 44 | 30 | /// |
| 45 | /// Be careful with this because it updates source files. This should not be | |
| 46 | /// used as part of the normal build process, but as a utility occasionally | |
| 47 | /// run by a developer with intent to modify source files and then commit | |
| 48 | /// those changes to version control. | |
| 49 | pub fn addCopyFileToSource(usf: *UpdateSourceFiles, source: std.Build.LazyPath, sub_path: []const u8) void { | |
| 50 | const b = usf.step.owner; | |
| 51 | usf.output_source_files.append(b.allocator, .{ | |
| 52 | .contents = .{ .copy = source }, | |
| 53 | .sub_path = sub_path, | |
| 31 | /// Because it updates source files, this should not be used as part of the | |
| 32 | /// normal build process, but as a utility occasionally run by a developer with | |
| 33 | /// intent to modify source files and then commit those changes to version | |
| 34 | /// control. | |
| 35 | pub fn addCopyFileToSource(usf: *UpdateSourceFiles, src_file: std.Build.LazyPath, sub_path: []const u8) void { | |
| 36 | const graph = usf.step.owner.graph; | |
| 37 | const wc = &graph.wip_configuration; | |
| 38 | const arena = graph.arena; | |
| 39 | ||
| 40 | usf.copies.append(arena, .{ | |
| 41 | .sub_path = wc.addString(sub_path) catch @panic("OOM"), | |
| 42 | .src_file = src_file.dupe(graph), | |
| 54 | 43 | }) catch @panic("OOM"); |
| 55 | source.addStepDependencies(&usf.step); | |
| 44 | ||
| 45 | src_file.addStepDependencies(&usf.step); | |
| 56 | 46 | } |
| 57 | 47 | |
| 58 | /// A path relative to the package root. | |
| 48 | /// Overwrites a path relative to the package root with the provided bytes. | |
| 59 | 49 | /// |
| 60 | /// Be careful with this because it updates source files. This should not be | |
| 61 | /// used as part of the normal build process, but as a utility occasionally | |
| 62 | /// run by a developer with intent to modify source files and then commit | |
| 63 | /// those changes to version control. | |
| 64 | pub fn addBytesToSource(usf: *UpdateSourceFiles, bytes: []const u8, sub_path: []const u8) void { | |
| 65 | const b = usf.step.owner; | |
| 66 | usf.output_source_files.append(b.allocator, .{ | |
| 67 | .contents = .{ .bytes = bytes }, | |
| 68 | .sub_path = sub_path, | |
| 50 | /// Because it updates source files, this should not be used as part of the | |
| 51 | /// normal build process, but as a utility occasionally run by a developer with | |
| 52 | /// intent to modify source files and then commit those changes to version | |
| 53 | /// control. | |
| 54 | pub fn addBytesToSource(usf: *UpdateSourceFiles, contents: []const u8, sub_path: []const u8) void { | |
| 55 | const graph = usf.step.owner.graph; | |
| 56 | const wc = &graph.wip_configuration; | |
| 57 | const arena = graph.arena; | |
| 58 | ||
| 59 | usf.embeds.append(arena, .{ | |
| 60 | .sub_path = wc.addString(sub_path) catch @panic("OOM"), | |
| 61 | .contents = wc.addBytes(contents) catch @panic("OOM"), | |
| 69 | 62 | }) catch @panic("OOM"); |
| 70 | 63 | } |
| 71 | ||
| 72 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 73 | _ = options; | |
| 74 | const b = step.owner; | |
| 75 | const io = b.graph.io; | |
| 76 | const usf: *UpdateSourceFiles = @fieldParentPtr("step", step); | |
| 77 | ||
| 78 | var any_miss = false; | |
| 79 | for (usf.output_source_files.items) |output_source_file| { | |
| 80 | if (fs.path.dirname(output_source_file.sub_path)) |dirname| { | |
| 81 | b.build_root.handle.createDirPath(io, dirname) catch |err| { | |
| 82 | return step.fail("unable to make path '{f}{s}': {t}", .{ b.build_root, dirname, err }); | |
| 83 | }; | |
| 84 | } | |
| 85 | switch (output_source_file.contents) { | |
| 86 | .bytes => |bytes| { | |
| 87 | b.build_root.handle.writeFile(io, .{ .sub_path = output_source_file.sub_path, .data = bytes }) catch |err| { | |
| 88 | return step.fail("unable to write file '{f}{s}': {t}", .{ | |
| 89 | b.build_root, output_source_file.sub_path, err, | |
| 90 | }); | |
| 91 | }; | |
| 92 | any_miss = true; | |
| 93 | }, | |
| 94 | .copy => |file_source| { | |
| 95 | if (!step.inputs.populated()) try step.addWatchInput(file_source); | |
| 96 | ||
| 97 | const source_path = file_source.getPath2(b, step); | |
| 98 | const prev_status = Io.Dir.updateFile( | |
| 99 | .cwd(), | |
| 100 | io, | |
| 101 | source_path, | |
| 102 | b.build_root.handle, | |
| 103 | output_source_file.sub_path, | |
| 104 | .{}, | |
| 105 | ) catch |err| { | |
| 106 | return step.fail("unable to update file from '{s}' to '{f}{s}': {t}", .{ | |
| 107 | source_path, b.build_root, output_source_file.sub_path, err, | |
| 108 | }); | |
| 109 | }; | |
| 110 | any_miss = any_miss or prev_status == .stale; | |
| 111 | }, | |
| 112 | } | |
| 113 | } | |
| 114 | ||
| 115 | step.result_cached = !any_miss; | |
| 116 | } |
lib/std/Build/Step/WriteFile.zig+113-328| ... | ... | @@ -4,21 +4,17 @@ |
| 4 | 4 | const WriteFile = @This(); |
| 5 | 5 | |
| 6 | 6 | const std = @import("std"); |
| 7 | const Io = std.Io; | |
| 8 | const Dir = std.Io.Dir; | |
| 9 | 7 | const Step = std.Build.Step; |
| 10 | const ArrayList = std.ArrayList; | |
| 11 | const assert = std.debug.assert; | |
| 8 | const Configuration = std.Build.Configuration; | |
| 12 | 9 | |
| 13 | 10 | step: Step, |
| 14 | ||
| 15 | /// The elements here are pointers because we need stable pointers for the GeneratedFile field. | |
| 16 | files: std.ArrayList(File), | |
| 17 | directories: std.ArrayList(Directory), | |
| 18 | generated_directory: std.Build.GeneratedFile, | |
| 11 | embeds: std.ArrayList(Embed) = .empty, | |
| 12 | copies: std.ArrayList(Copy) = .empty, | |
| 13 | directories: std.ArrayList(Directory) = .empty, | |
| 14 | generated_directory: Configuration.GeneratedFileIndex, | |
| 19 | 15 | mode: Mode = .whole_cached, |
| 20 | 16 | |
| 21 | pub const base_id: Step.Id = .write_file; | |
| 17 | pub const base_tag: Step.Tag = .write_file; | |
| 22 | 18 | |
| 23 | 19 | pub const Mode = union(enum) { |
| 24 | 20 | /// Default mode. Integrates with the cache system. The directory should be |
| ... | ... | @@ -37,363 +33,152 @@ pub const Mode = union(enum) { |
| 37 | 33 | mutate: std.Build.LazyPath, |
| 38 | 34 | }; |
| 39 | 35 | |
| 40 | pub const File = struct { | |
| 41 | sub_path: []const u8, | |
| 42 | contents: Contents, | |
| 43 | }; | |
| 44 | ||
| 45 | pub const Directory = struct { | |
| 46 | source: std.Build.LazyPath, | |
| 47 | sub_path: []const u8, | |
| 48 | options: Options, | |
| 49 | ||
| 50 | pub const Options = struct { | |
| 51 | /// File paths that end in any of these suffixes will be excluded from copying. | |
| 52 | exclude_extensions: []const []const u8 = &.{}, | |
| 53 | /// Only file paths that end in any of these suffixes will be included in copying. | |
| 54 | /// `null` means that all suffixes will be included. | |
| 55 | /// `exclude_extensions` takes precedence over `include_extensions`. | |
| 56 | include_extensions: ?[]const []const u8 = null, | |
| 57 | ||
| 58 | pub fn dupe(opts: Options, b: *std.Build) Options { | |
| 59 | return .{ | |
| 60 | .exclude_extensions = b.dupeStrings(opts.exclude_extensions), | |
| 61 | .include_extensions = if (opts.include_extensions) |incs| b.dupeStrings(incs) else null, | |
| 62 | }; | |
| 63 | } | |
| 36 | pub const Embed = Configuration.Step.WriteFile.Embed; | |
| 64 | 37 | |
| 65 | pub fn pathIncluded(opts: Options, path: []const u8) bool { | |
| 66 | for (opts.exclude_extensions) |ext| { | |
| 67 | if (std.mem.endsWith(u8, path, ext)) | |
| 68 | return false; | |
| 69 | } | |
| 70 | if (opts.include_extensions) |incs| { | |
| 71 | for (incs) |inc| { | |
| 72 | if (std.mem.endsWith(u8, path, inc)) | |
| 73 | return true; | |
| 74 | } else { | |
| 75 | return false; | |
| 76 | } | |
| 77 | } | |
| 78 | return true; | |
| 79 | } | |
| 80 | }; | |
| 38 | pub const Copy = struct { | |
| 39 | sub_path: Configuration.String, | |
| 40 | src_file: std.Build.LazyPath, | |
| 81 | 41 | }; |
| 82 | 42 | |
| 83 | pub const Contents = union(enum) { | |
| 84 | bytes: []const u8, | |
| 85 | copy: std.Build.LazyPath, | |
| 43 | pub const Directory = struct { | |
| 44 | sub_path: Configuration.String, | |
| 45 | src_path: std.Build.LazyPath, | |
| 46 | exclude_extensions: Configuration.OptionalStringList, | |
| 47 | include_extensions: Configuration.OptionalStringList, | |
| 86 | 48 | }; |
| 87 | 49 | |
| 88 | 50 | pub fn create(owner: *std.Build) *WriteFile { |
| 89 | const write_file = owner.allocator.create(WriteFile) catch @panic("OOM"); | |
| 90 | write_file.* = .{ | |
| 91 | .step = Step.init(.{ | |
| 92 | .id = base_id, | |
| 51 | const graph = owner.graph; | |
| 52 | const wf = graph.create(WriteFile); | |
| 53 | wf.* = .{ | |
| 54 | .step = .init(.{ | |
| 55 | .tag = base_tag, | |
| 93 | 56 | .name = "WriteFile", |
| 94 | 57 | .owner = owner, |
| 95 | .makeFn = make, | |
| 96 | 58 | }), |
| 97 | .files = .empty, | |
| 98 | .directories = .empty, | |
| 99 | .generated_directory = .{ .step = &write_file.step }, | |
| 59 | .generated_directory = graph.addGeneratedFile(&wf.step), | |
| 100 | 60 | }; |
| 101 | return write_file; | |
| 61 | return wf; | |
| 102 | 62 | } |
| 103 | 63 | |
| 104 | pub fn add(write_file: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.LazyPath { | |
| 105 | const b = write_file.step.owner; | |
| 106 | const gpa = b.allocator; | |
| 107 | const file = File{ | |
| 108 | .sub_path = b.dupePath(sub_path), | |
| 109 | .contents = .{ .bytes = b.dupe(bytes) }, | |
| 110 | }; | |
| 111 | write_file.files.append(gpa, file) catch @panic("OOM"); | |
| 112 | write_file.maybeUpdateName(); | |
| 64 | /// Writes `contents` to a file at `sub_path` relative to the output | |
| 65 | /// directory. | |
| 66 | /// | |
| 67 | /// `sub_path` may be a basename, or it may include subdirectories, which are | |
| 68 | /// created as needed. | |
| 69 | pub fn add(wf: *WriteFile, sub_path: []const u8, contents: []const u8) std.Build.LazyPath { | |
| 70 | const graph = wf.step.owner.graph; | |
| 71 | const wc = &graph.wip_configuration; | |
| 72 | const arena = graph.arena; | |
| 73 | ||
| 74 | wf.embeds.append(arena, .{ | |
| 75 | .sub_path = wc.addString(sub_path) catch @panic("OOM"), | |
| 76 | .contents = wc.addBytes(contents) catch @panic("OOM"), | |
| 77 | }) catch @panic("OOM"); | |
| 78 | ||
| 79 | wf.maybeUpdateName(); | |
| 80 | ||
| 113 | 81 | return .{ |
| 114 | 82 | .generated = .{ |
| 115 | .file = &write_file.generated_directory, | |
| 116 | .sub_path = file.sub_path, | |
| 83 | .index = wf.generated_directory, | |
| 84 | .sub_path = graph.dupeString(sub_path), | |
| 117 | 85 | }, |
| 118 | 86 | }; |
| 119 | 87 | } |
| 120 | 88 | |
| 121 | /// Place the file into the generated directory within the local cache, | |
| 122 | /// along with all the rest of the files added to this step. The parameter | |
| 123 | /// here is the destination path relative to the local cache directory | |
| 124 | /// associated with this WriteFile. It may be a basename, or it may | |
| 125 | /// include sub-directories, in which case this step will ensure the | |
| 126 | /// required sub-path exists. | |
| 127 | /// This is the option expected to be used most commonly with `addCopyFile`. | |
| 128 | pub fn addCopyFile(write_file: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath { | |
| 129 | const b = write_file.step.owner; | |
| 130 | const gpa = b.allocator; | |
| 131 | const file = File{ | |
| 132 | .sub_path = b.dupePath(sub_path), | |
| 133 | .contents = .{ .copy = source }, | |
| 134 | }; | |
| 135 | write_file.files.append(gpa, file) catch @panic("OOM"); | |
| 89 | /// Copies the provided file to `sub_path` relative to the output directory. | |
| 90 | /// | |
| 91 | /// `sub_path` may be a basename, or it may include subdirectories, which are | |
| 92 | /// created as needed. | |
| 93 | pub fn addCopyFile(wf: *WriteFile, src_file: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath { | |
| 94 | const graph = wf.step.owner.graph; | |
| 95 | const wc = &graph.wip_configuration; | |
| 96 | const arena = graph.arena; | |
| 136 | 97 | |
| 137 | write_file.maybeUpdateName(); | |
| 138 | source.addStepDependencies(&write_file.step); | |
| 139 | return .{ | |
| 140 | .generated = .{ | |
| 141 | .file = &write_file.generated_directory, | |
| 142 | .sub_path = file.sub_path, | |
| 143 | }, | |
| 144 | }; | |
| 98 | wf.copies.append(arena, .{ | |
| 99 | .sub_path = wc.addString(sub_path) catch @panic("OOM"), | |
| 100 | .src_file = src_file.dupe(graph), | |
| 101 | }) catch @panic("OOM"); | |
| 102 | ||
| 103 | wf.maybeUpdateName(); | |
| 104 | ||
| 105 | src_file.addStepDependencies(&wf.step); | |
| 106 | ||
| 107 | return .{ .generated = .{ | |
| 108 | .index = wf.generated_directory, | |
| 109 | .sub_path = graph.dupePath(sub_path), | |
| 110 | } }; | |
| 145 | 111 | } |
| 146 | 112 | |
| 147 | /// Copy files matching the specified exclude/include patterns to the specified subdirectory | |
| 148 | /// relative to this step's generated directory. | |
| 113 | pub const CopyDirectoryOptions = struct { | |
| 114 | /// File paths that end in any of these suffixes will be excluded from copying. | |
| 115 | exclude_extensions: []const []const u8 = &.{}, | |
| 116 | /// Only file paths that end in any of these suffixes will be included in copying. | |
| 117 | /// `null` means that all suffixes will be included. | |
| 118 | /// `exclude_extensions` takes precedence over `include_extensions`. | |
| 119 | include_extensions: ?[]const []const u8 = null, | |
| 120 | }; | |
| 121 | ||
| 122 | /// Copy files matching the specified exclude/include patterns to the specified | |
| 123 | /// subdirectory relative to this step's generated directory. | |
| 124 | /// | |
| 149 | 125 | /// The returned value is a lazy path to the generated subdirectory. |
| 150 | 126 | pub fn addCopyDirectory( |
| 151 | write_file: *WriteFile, | |
| 152 | source: std.Build.LazyPath, | |
| 127 | wf: *WriteFile, | |
| 128 | src_path: std.Build.LazyPath, | |
| 153 | 129 | sub_path: []const u8, |
| 154 | options: Directory.Options, | |
| 130 | options: CopyDirectoryOptions, | |
| 155 | 131 | ) std.Build.LazyPath { |
| 156 | const b = write_file.step.owner; | |
| 157 | const gpa = b.allocator; | |
| 158 | const dir = Directory{ | |
| 159 | .source = source.dupe(b), | |
| 160 | .sub_path = b.dupePath(sub_path), | |
| 161 | .options = options.dupe(b), | |
| 162 | }; | |
| 163 | write_file.directories.append(gpa, dir) catch @panic("OOM"); | |
| 132 | const graph = wf.step.owner.graph; | |
| 133 | const wc = &graph.wip_configuration; | |
| 134 | const arena = graph.arena; | |
| 135 | ||
| 136 | wf.directories.append(arena, .{ | |
| 137 | .sub_path = wc.addString(sub_path) catch @panic("OOM"), | |
| 138 | .src_path = src_path.dupe(graph), | |
| 139 | .exclude_extensions = if (options.exclude_extensions.len != 0) | |
| 140 | .init(wc.addStringList(options.exclude_extensions) catch @panic("OOM")) | |
| 141 | else | |
| 142 | .none, | |
| 143 | .include_extensions = if (options.include_extensions) |list| | |
| 144 | .init(wc.addStringList(list) catch @panic("OOM")) | |
| 145 | else | |
| 146 | .none, | |
| 147 | }) catch @panic("OOM"); | |
| 148 | ||
| 149 | wf.maybeUpdateName(); | |
| 150 | ||
| 151 | src_path.addStepDependencies(&wf.step); | |
| 164 | 152 | |
| 165 | write_file.maybeUpdateName(); | |
| 166 | source.addStepDependencies(&write_file.step); | |
| 167 | 153 | return .{ |
| 168 | 154 | .generated = .{ |
| 169 | .file = &write_file.generated_directory, | |
| 170 | .sub_path = dir.sub_path, | |
| 155 | .index = wf.generated_directory, | |
| 156 | .sub_path = graph.dupePath(sub_path), | |
| 171 | 157 | }, |
| 172 | 158 | }; |
| 173 | 159 | } |
| 174 | 160 | |
| 175 | 161 | /// Returns a `LazyPath` representing the base directory that contains all the |
| 176 | 162 | /// files from this `WriteFile`. |
| 177 | pub fn getDirectory(write_file: *WriteFile) std.Build.LazyPath { | |
| 178 | return .{ .generated = .{ .file = &write_file.generated_directory } }; | |
| 163 | pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath { | |
| 164 | return .{ .generated = .{ .index = wf.generated_directory } }; | |
| 179 | 165 | } |
| 180 | 166 | |
| 181 | fn maybeUpdateName(write_file: *WriteFile) void { | |
| 182 | if (write_file.files.items.len == 1 and write_file.directories.items.len == 0) { | |
| 167 | fn maybeUpdateName(wf: *WriteFile) void { | |
| 168 | const graph = wf.step.owner.graph; | |
| 169 | const wc = &graph.wip_configuration; | |
| 170 | const files_count = wf.embeds.items.len + wf.copies.items.len; | |
| 171 | if (files_count == 1 and wf.directories.items.len == 0) { | |
| 183 | 172 | // First time adding a file; update name. |
| 184 | if (std.mem.eql(u8, write_file.step.name, "WriteFile")) { | |
| 185 | write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.files.items[0].sub_path}); | |
| 173 | const sub_path = if (wf.embeds.items.len == 1) wf.embeds.items[0].sub_path else wf.copies.items[0].sub_path; | |
| 174 | if (std.mem.eql(u8, wf.step.name, "WriteFile")) { | |
| 175 | wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wc.stringSlice(sub_path)}); | |
| 186 | 176 | } |
| 187 | } else if (write_file.directories.items.len == 1 and write_file.files.items.len == 0) { | |
| 177 | } else if (wf.directories.items.len == 1 and files_count == 0) { | |
| 188 | 178 | // First time adding a directory; update name. |
| 189 | if (std.mem.eql(u8, write_file.step.name, "WriteFile")) { | |
| 190 | write_file.step.name = write_file.step.owner.fmt("WriteFile {s}", .{write_file.directories.items[0].sub_path}); | |
| 191 | } | |
| 192 | } | |
| 193 | } | |
| 194 | ||
| 195 | fn make(step: *Step, options: Step.MakeOptions) !void { | |
| 196 | _ = options; | |
| 197 | const b = step.owner; | |
| 198 | const graph = b.graph; | |
| 199 | const io = graph.io; | |
| 200 | const arena = b.allocator; | |
| 201 | const gpa = graph.cache.gpa; | |
| 202 | const write_file: *WriteFile = @fieldParentPtr("step", step); | |
| 203 | ||
| 204 | const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len); | |
| 205 | var open_dirs_count: usize = 0; | |
| 206 | defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]); | |
| 207 | ||
| 208 | switch (write_file.mode) { | |
| 209 | .whole_cached => { | |
| 210 | step.clearWatchInputs(); | |
| 211 | ||
| 212 | // The cache is used here not really as a way to speed things up - because writing | |
| 213 | // the data to a file would probably be very fast - but as a way to find a canonical | |
| 214 | // location to put build artifacts. | |
| 215 | ||
| 216 | // If, for example, a hard-coded path was used as the location to put WriteFile | |
| 217 | // files, then two WriteFiles executing in parallel might clobber each other. | |
| 218 | ||
| 219 | var man = b.graph.cache.obtain(); | |
| 220 | defer man.deinit(); | |
| 221 | ||
| 222 | for (write_file.files.items) |file| { | |
| 223 | man.hash.addBytes(file.sub_path); | |
| 224 | ||
| 225 | switch (file.contents) { | |
| 226 | .bytes => |bytes| { | |
| 227 | man.hash.addBytes(bytes); | |
| 228 | }, | |
| 229 | .copy => |lazy_path| { | |
| 230 | const path = lazy_path.getPath3(b, step); | |
| 231 | _ = try man.addFilePath(path, null); | |
| 232 | try step.addWatchInput(lazy_path); | |
| 233 | }, | |
| 234 | } | |
| 235 | } | |
| 236 | ||
| 237 | for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| { | |
| 238 | man.hash.addBytes(dir.sub_path); | |
| 239 | for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext); | |
| 240 | if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc); | |
| 241 | ||
| 242 | const need_derived_inputs = try step.addDirectoryWatchInput(dir.source); | |
| 243 | const src_dir_path = dir.source.getPath3(b, step); | |
| 244 | ||
| 245 | var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| { | |
| 246 | return step.fail("unable to open source directory '{f}': {s}", .{ | |
| 247 | src_dir_path, @errorName(err), | |
| 248 | }); | |
| 249 | }; | |
| 250 | open_dir_cache_elem.* = src_dir; | |
| 251 | open_dirs_count += 1; | |
| 252 | ||
| 253 | var it = try src_dir.walk(gpa); | |
| 254 | defer it.deinit(); | |
| 255 | while (try it.next(io)) |entry| { | |
| 256 | if (!dir.options.pathIncluded(entry.path)) continue; | |
| 257 | ||
| 258 | switch (entry.kind) { | |
| 259 | .directory => { | |
| 260 | if (need_derived_inputs) { | |
| 261 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 262 | try step.addDirectoryWatchInputFromPath(entry_path); | |
| 263 | } | |
| 264 | }, | |
| 265 | .file => { | |
| 266 | const entry_path = try src_dir_path.join(arena, entry.path); | |
| 267 | _ = try man.addFilePath(entry_path, null); | |
| 268 | }, | |
| 269 | else => continue, | |
| 270 | } | |
| 271 | } | |
| 272 | } | |
| 273 | ||
| 274 | if (try step.cacheHit(&man)) { | |
| 275 | const digest = man.final(); | |
| 276 | write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest }); | |
| 277 | assert(step.result_cached); | |
| 278 | return; | |
| 279 | } | |
| 280 | ||
| 281 | const digest = man.final(); | |
| 282 | const cache_path = "o" ++ Dir.path.sep_str ++ digest; | |
| 283 | ||
| 284 | write_file.generated_directory.path = try b.cache_root.join(arena, &.{cache_path}); | |
| 285 | ||
| 286 | try operate(write_file, open_dir_cache, .{ | |
| 287 | .root_dir = b.cache_root, | |
| 288 | .sub_path = cache_path, | |
| 289 | }); | |
| 290 | ||
| 291 | try step.writeManifest(&man); | |
| 292 | }, | |
| 293 | .tmp => { | |
| 294 | step.result_cached = false; | |
| 295 | ||
| 296 | var rand_int: u64 = undefined; | |
| 297 | io.random(@ptrCast(&rand_int)); | |
| 298 | const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); | |
| 299 | ||
| 300 | write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path}); | |
| 301 | ||
| 302 | try operate(write_file, open_dir_cache, .{ | |
| 303 | .root_dir = b.cache_root, | |
| 304 | .sub_path = tmp_dir_sub_path, | |
| 305 | }); | |
| 306 | }, | |
| 307 | .mutate => |lp| { | |
| 308 | step.result_cached = false; | |
| 309 | const root_path = try lp.getPath4(b, step); | |
| 310 | write_file.generated_directory.path = try root_path.toString(arena); | |
| 311 | try operate(write_file, open_dir_cache, root_path); | |
| 312 | }, | |
| 313 | } | |
| 314 | } | |
| 315 | ||
| 316 | fn operate(write_file: *WriteFile, open_dir_cache: []const Io.Dir, root_path: std.Build.Cache.Path) !void { | |
| 317 | const step = &write_file.step; | |
| 318 | const b = step.owner; | |
| 319 | const io = b.graph.io; | |
| 320 | const gpa = b.graph.cache.gpa; | |
| 321 | const arena = b.allocator; | |
| 322 | ||
| 323 | var cache_dir = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err| | |
| 324 | return step.fail("unable to make path {f}: {t}", .{ root_path, err }); | |
| 325 | defer cache_dir.close(io); | |
| 326 | ||
| 327 | for (write_file.files.items) |file| { | |
| 328 | if (Dir.path.dirname(file.sub_path)) |dirname| { | |
| 329 | cache_dir.createDirPath(io, dirname) catch |err| { | |
| 330 | return step.fail("unable to make path '{f}{c}{s}': {t}", .{ | |
| 331 | root_path, Dir.path.sep, dirname, err, | |
| 332 | }); | |
| 333 | }; | |
| 334 | } | |
| 335 | switch (file.contents) { | |
| 336 | .bytes => |bytes| { | |
| 337 | cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| { | |
| 338 | return step.fail("unable to write file '{f}{c}{s}': {t}", .{ | |
| 339 | root_path, Dir.path.sep, file.sub_path, err, | |
| 340 | }); | |
| 341 | }; | |
| 342 | }, | |
| 343 | .copy => |file_source| { | |
| 344 | const source_path = file_source.getPath2(b, step); | |
| 345 | const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| { | |
| 346 | return step.fail("unable to update file from '{s}' to '{f}{c}{s}': {t}", .{ | |
| 347 | source_path, root_path, Dir.path.sep, file.sub_path, err, | |
| 348 | }); | |
| 349 | }; | |
| 350 | // At this point we already will mark the step as a cache miss. | |
| 351 | // But this is kind of a partial cache hit since individual | |
| 352 | // file copies may be avoided. Oh well, this information is | |
| 353 | // discarded. | |
| 354 | _ = prev_status; | |
| 355 | }, | |
| 356 | } | |
| 357 | } | |
| 358 | ||
| 359 | for (write_file.directories.items, open_dir_cache) |dir, already_open_dir| { | |
| 360 | const src_dir_path = dir.source.getPath3(b, step); | |
| 361 | const dest_dirname = dir.sub_path; | |
| 362 | ||
| 363 | if (dest_dirname.len != 0) { | |
| 364 | cache_dir.createDirPath(io, dest_dirname) catch |err| { | |
| 365 | return step.fail("unable to make path '{f}{c}{s}': {t}", .{ | |
| 366 | root_path, Dir.path.sep, dest_dirname, err, | |
| 367 | }); | |
| 368 | }; | |
| 369 | } | |
| 370 | ||
| 371 | var it = try already_open_dir.walk(gpa); | |
| 372 | defer it.deinit(); | |
| 373 | while (try it.next(io)) |entry| { | |
| 374 | if (!dir.options.pathIncluded(entry.path)) continue; | |
| 375 | ||
| 376 | const src_entry_path = try src_dir_path.join(arena, entry.path); | |
| 377 | const dest_path = b.pathJoin(&.{ dest_dirname, entry.path }); | |
| 378 | switch (entry.kind) { | |
| 379 | .directory => try cache_dir.createDirPath(io, dest_path), | |
| 380 | .file => { | |
| 381 | const prev_status = Io.Dir.updateFile( | |
| 382 | src_entry_path.root_dir.handle, | |
| 383 | io, | |
| 384 | src_entry_path.sub_path, | |
| 385 | cache_dir, | |
| 386 | dest_path, | |
| 387 | .{}, | |
| 388 | ) catch |err| { | |
| 389 | return step.fail("unable to update file from '{f}' to '{f}{c}{s}': {t}", .{ | |
| 390 | src_entry_path, root_path, Dir.path.sep, dest_path, err, | |
| 391 | }); | |
| 392 | }; | |
| 393 | _ = prev_status; | |
| 394 | }, | |
| 395 | else => continue, | |
| 396 | } | |
| 179 | const dir_name = wc.stringSlice(wf.directories.items[0].sub_path); | |
| 180 | if (std.mem.eql(u8, wf.step.name, "WriteFile")) { | |
| 181 | wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{dir_name}); | |
| 397 | 182 | } |
| 398 | 183 | } |
| 399 | 184 | } |
lib/std/Build/Watch.zig deleted-968| ... | ... | @@ -1,968 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | ||
| 3 | const std = @import("../std.zig"); | |
| 4 | const Io = std.Io; | |
| 5 | const Step = std.Build.Step; | |
| 6 | const Allocator = std.mem.Allocator; | |
| 7 | const assert = std.debug.assert; | |
| 8 | const fatal = std.process.fatal; | |
| 9 | const Watch = @This(); | |
| 10 | const FsEvents = @import("Watch/FsEvents.zig"); | |
| 11 | ||
| 12 | os: Os, | |
| 13 | /// The number to show as the number of directories being watched. | |
| 14 | dir_count: usize, | |
| 15 | // These fields are common to most implementations so are kept here for simplicity. | |
| 16 | // They are `undefined` on implementations which do not utilize then. | |
| 17 | dir_table: DirTable, | |
| 18 | generation: Generation, | |
| 19 | ||
| 20 | pub const have_impl = Os != void; | |
| 21 | ||
| 22 | /// Key is the directory to watch which contains one or more files we are | |
| 23 | /// interested in noticing changes to. | |
| 24 | /// | |
| 25 | /// Value is generation. | |
| 26 | const DirTable = std.ArrayHashMapUnmanaged(Cache.Path, void, Cache.Path.TableAdapter, false); | |
| 27 | ||
| 28 | /// Special key of "." means any changes in this directory trigger the steps. | |
| 29 | const ReactionSet = std.StringArrayHashMapUnmanaged(StepSet); | |
| 30 | const StepSet = std.AutoArrayHashMapUnmanaged(*Step, Generation); | |
| 31 | ||
| 32 | const Generation = u8; | |
| 33 | ||
| 34 | const Hash = std.hash.Wyhash; | |
| 35 | const Cache = std.Build.Cache; | |
| 36 | ||
| 37 | const Os = switch (builtin.os.tag) { | |
| 38 | .linux => struct { | |
| 39 | const posix = std.posix; | |
| 40 | ||
| 41 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. | |
| 42 | handle_table: HandleTable, | |
| 43 | /// fanotify file descriptors are keyed by mount id since marks | |
| 44 | /// are limited to a single filesystem. | |
| 45 | poll_fds: std.AutoArrayHashMapUnmanaged(MountId, posix.pollfd), | |
| 46 | ||
| 47 | const MountId = i32; | |
| 48 | const HandleTable = std.ArrayHashMapUnmanaged(FileHandle, struct { mount_id: MountId, reaction_set: ReactionSet }, FileHandle.Adapter, false); | |
| 49 | ||
| 50 | const fan_mask: std.os.linux.fanotify.MarkMask = .{ | |
| 51 | .CLOSE_WRITE = true, | |
| 52 | .CREATE = true, | |
| 53 | .DELETE = true, | |
| 54 | .DELETE_SELF = true, | |
| 55 | .EVENT_ON_CHILD = true, | |
| 56 | .MOVED_FROM = true, | |
| 57 | .MOVED_TO = true, | |
| 58 | .MOVE_SELF = true, | |
| 59 | .ONDIR = true, | |
| 60 | }; | |
| 61 | ||
| 62 | const FileHandle = struct { | |
| 63 | handle: *align(1) std.os.linux.file_handle, | |
| 64 | ||
| 65 | fn clone(lfh: FileHandle, gpa: Allocator) Allocator.Error!FileHandle { | |
| 66 | const bytes = lfh.slice(); | |
| 67 | const new_ptr = try gpa.alignedAlloc( | |
| 68 | u8, | |
| 69 | .of(std.os.linux.file_handle), | |
| 70 | @sizeOf(std.os.linux.file_handle) + bytes.len, | |
| 71 | ); | |
| 72 | const new_header: *std.os.linux.file_handle = @ptrCast(new_ptr); | |
| 73 | new_header.* = lfh.handle.*; | |
| 74 | const new: FileHandle = .{ .handle = new_header }; | |
| 75 | @memcpy(new.slice(), lfh.slice()); | |
| 76 | return new; | |
| 77 | } | |
| 78 | ||
| 79 | fn destroy(lfh: FileHandle, gpa: Allocator) void { | |
| 80 | const ptr: [*]u8 = @ptrCast(lfh.handle); | |
| 81 | const allocated_slice = ptr[0 .. @sizeOf(std.os.linux.file_handle) + lfh.handle.handle_bytes]; | |
| 82 | return gpa.free(allocated_slice); | |
| 83 | } | |
| 84 | ||
| 85 | fn slice(lfh: FileHandle) []u8 { | |
| 86 | const ptr: [*]u8 = &lfh.handle.f_handle; | |
| 87 | return ptr[0..lfh.handle.handle_bytes]; | |
| 88 | } | |
| 89 | ||
| 90 | const Adapter = struct { | |
| 91 | pub fn hash(self: Adapter, a: FileHandle) u32 { | |
| 92 | _ = self; | |
| 93 | const unsigned_type: u32 = @bitCast(a.handle.handle_type); | |
| 94 | return @truncate(Hash.hash(unsigned_type, a.slice())); | |
| 95 | } | |
| 96 | pub fn eql(self: Adapter, a: FileHandle, b: FileHandle, b_index: usize) bool { | |
| 97 | _ = self; | |
| 98 | _ = b_index; | |
| 99 | return a.handle.handle_type == b.handle.handle_type and std.mem.eql(u8, a.slice(), b.slice()); | |
| 100 | } | |
| 101 | }; | |
| 102 | }; | |
| 103 | ||
| 104 | fn init(cwd_path: []const u8) !Watch { | |
| 105 | _ = cwd_path; | |
| 106 | return .{ | |
| 107 | .dir_table = .{}, | |
| 108 | .dir_count = 0, | |
| 109 | .os = switch (builtin.os.tag) { | |
| 110 | .linux => .{ | |
| 111 | .handle_table = .{}, | |
| 112 | .poll_fds = .{}, | |
| 113 | }, | |
| 114 | else => {}, | |
| 115 | }, | |
| 116 | .generation = 0, | |
| 117 | }; | |
| 118 | } | |
| 119 | ||
| 120 | fn getDirHandle(gpa: Allocator, path: std.Build.Cache.Path, mount_id: *MountId) !FileHandle { | |
| 121 | var file_handle_buffer: [@sizeOf(std.os.linux.file_handle) + 128]u8 align(@alignOf(std.os.linux.file_handle)) = undefined; | |
| 122 | var buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 123 | const adjusted_path = if (path.sub_path.len == 0) "./" else std.fmt.bufPrint(&buf, "{s}/", .{ | |
| 124 | path.sub_path, | |
| 125 | }) catch return error.NameTooLong; | |
| 126 | const stack_ptr: *std.os.linux.file_handle = @ptrCast(&file_handle_buffer); | |
| 127 | stack_ptr.handle_bytes = file_handle_buffer.len - @sizeOf(std.os.linux.file_handle); | |
| 128 | try posix.name_to_handle_at(path.root_dir.handle.handle, adjusted_path, stack_ptr, mount_id, std.os.linux.AT.HANDLE_FID); | |
| 129 | const stack_lfh: FileHandle = .{ .handle = stack_ptr }; | |
| 130 | return stack_lfh.clone(gpa); | |
| 131 | } | |
| 132 | ||
| 133 | fn markDirtySteps(w: *Watch, gpa: Allocator, fan_fd: posix.fd_t) !bool { | |
| 134 | const fanotify = std.os.linux.fanotify; | |
| 135 | const M = fanotify.event_metadata; | |
| 136 | var events_buf: [256 + 4096]u8 = undefined; | |
| 137 | var any_dirty = false; | |
| 138 | while (true) { | |
| 139 | var len = posix.read(fan_fd, &events_buf) catch |err| switch (err) { | |
| 140 | error.WouldBlock => return any_dirty, | |
| 141 | else => |e| return e, | |
| 142 | }; | |
| 143 | var meta: [*]align(1) M = @ptrCast(&events_buf); | |
| 144 | while (len >= @sizeOf(M) and meta[0].event_len >= @sizeOf(M) and meta[0].event_len <= len) : ({ | |
| 145 | len -= meta[0].event_len; | |
| 146 | meta = @ptrCast(@as([*]u8, @ptrCast(meta)) + meta[0].event_len); | |
| 147 | }) { | |
| 148 | assert(meta[0].vers == M.VERSION); | |
| 149 | if (meta[0].mask.Q_OVERFLOW) { | |
| 150 | any_dirty = true; | |
| 151 | std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); | |
| 152 | markAllFilesDirty(w, gpa); | |
| 153 | return true; | |
| 154 | } | |
| 155 | const fid: *align(1) fanotify.event_info_fid = @ptrCast(meta + 1); | |
| 156 | switch (fid.hdr.info_type) { | |
| 157 | .DFID_NAME => { | |
| 158 | const file_handle: *align(1) std.os.linux.file_handle = @ptrCast(&fid.handle); | |
| 159 | const file_name_z: [*:0]u8 = @ptrCast((&file_handle.f_handle).ptr + file_handle.handle_bytes); | |
| 160 | const file_name = std.mem.span(file_name_z); | |
| 161 | const lfh: FileHandle = .{ .handle = file_handle }; | |
| 162 | if (w.os.handle_table.getPtr(lfh)) |value| { | |
| 163 | if (value.reaction_set.getPtr(".")) |glob_set| | |
| 164 | any_dirty = markStepSetDirty(gpa, glob_set, any_dirty); | |
| 165 | if (value.reaction_set.getPtr(file_name)) |step_set| | |
| 166 | any_dirty = markStepSetDirty(gpa, step_set, any_dirty); | |
| 167 | } | |
| 168 | }, | |
| 169 | else => |t| std.log.warn("unexpected fanotify event '{s}'", .{@tagName(t)}), | |
| 170 | } | |
| 171 | } | |
| 172 | } | |
| 173 | } | |
| 174 | ||
| 175 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { | |
| 176 | // Add missing marks and note persisted ones. | |
| 177 | for (steps) |step| { | |
| 178 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | |
| 179 | const reaction_set = rs: { | |
| 180 | const gop = try w.dir_table.getOrPut(gpa, path); | |
| 181 | if (!gop.found_existing) { | |
| 182 | var mount_id: MountId = undefined; | |
| 183 | const dir_handle = getDirHandle(gpa, path, &mount_id) catch |err| switch (err) { | |
| 184 | error.FileNotFound => { | |
| 185 | std.debug.assert(w.dir_table.swapRemove(path)); | |
| 186 | continue; | |
| 187 | }, | |
| 188 | else => return err, | |
| 189 | }; | |
| 190 | const fan_fd = blk: { | |
| 191 | const fd_gop = try w.os.poll_fds.getOrPut(gpa, mount_id); | |
| 192 | if (!fd_gop.found_existing) { | |
| 193 | const fan_fd = std.posix.fanotify_init(.{ | |
| 194 | .CLASS = .NOTIF, | |
| 195 | .CLOEXEC = true, | |
| 196 | .NONBLOCK = true, | |
| 197 | .REPORT_NAME = true, | |
| 198 | .REPORT_DIR_FID = true, | |
| 199 | .REPORT_FID = true, | |
| 200 | .REPORT_TARGET_FID = true, | |
| 201 | }, 0) catch |err| switch (err) { | |
| 202 | error.UnsupportedFlags => fatal("fanotify_init failed due to old kernel; requires 5.17+", .{}), | |
| 203 | else => |e| return e, | |
| 204 | }; | |
| 205 | fd_gop.value_ptr.* = .{ | |
| 206 | .fd = fan_fd, | |
| 207 | .events = std.posix.POLL.IN, | |
| 208 | .revents = undefined, | |
| 209 | }; | |
| 210 | } | |
| 211 | break :blk fd_gop.value_ptr.*.fd; | |
| 212 | }; | |
| 213 | // `dir_handle` may already be present in the table in | |
| 214 | // the case that we have multiple Cache.Path instances | |
| 215 | // that compare inequal but ultimately point to the same | |
| 216 | // directory on the file system. | |
| 217 | // In such case, we must revert adding this directory, but keep | |
| 218 | // the additions to the step set. | |
| 219 | const dh_gop = try w.os.handle_table.getOrPut(gpa, dir_handle); | |
| 220 | if (dh_gop.found_existing) { | |
| 221 | _ = w.dir_table.pop(); | |
| 222 | } else { | |
| 223 | assert(dh_gop.index == gop.index); | |
| 224 | dh_gop.value_ptr.* = .{ .mount_id = mount_id, .reaction_set = .{} }; | |
| 225 | posix.fanotify_mark(fan_fd, .{ | |
| 226 | .ADD = true, | |
| 227 | .ONLYDIR = true, | |
| 228 | }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| { | |
| 229 | fatal("unable to watch {f}: {s}", .{ path, @errorName(err) }); | |
| 230 | }; | |
| 231 | } | |
| 232 | break :rs &dh_gop.value_ptr.reaction_set; | |
| 233 | } | |
| 234 | break :rs &w.os.handle_table.values()[gop.index].reaction_set; | |
| 235 | }; | |
| 236 | for (files.items) |basename| { | |
| 237 | const gop = try reaction_set.getOrPut(gpa, basename); | |
| 238 | if (!gop.found_existing) gop.value_ptr.* = .{}; | |
| 239 | try gop.value_ptr.put(gpa, step, w.generation); | |
| 240 | } | |
| 241 | } | |
| 242 | } | |
| 243 | ||
| 244 | { | |
| 245 | // Remove marks for files that are no longer inputs. | |
| 246 | var i: usize = 0; | |
| 247 | while (i < w.os.handle_table.entries.len) { | |
| 248 | { | |
| 249 | const reaction_set = &w.os.handle_table.values()[i].reaction_set; | |
| 250 | var step_set_i: usize = 0; | |
| 251 | while (step_set_i < reaction_set.entries.len) { | |
| 252 | const step_set = &reaction_set.values()[step_set_i]; | |
| 253 | var dirent_i: usize = 0; | |
| 254 | while (dirent_i < step_set.entries.len) { | |
| 255 | const generations = step_set.values(); | |
| 256 | if (generations[dirent_i] == w.generation) { | |
| 257 | dirent_i += 1; | |
| 258 | continue; | |
| 259 | } | |
| 260 | step_set.swapRemoveAt(dirent_i); | |
| 261 | } | |
| 262 | if (step_set.entries.len > 0) { | |
| 263 | step_set_i += 1; | |
| 264 | continue; | |
| 265 | } | |
| 266 | reaction_set.swapRemoveAt(step_set_i); | |
| 267 | } | |
| 268 | if (reaction_set.entries.len > 0) { | |
| 269 | i += 1; | |
| 270 | continue; | |
| 271 | } | |
| 272 | } | |
| 273 | ||
| 274 | const path = w.dir_table.keys()[i]; | |
| 275 | ||
| 276 | const mount_id = w.os.handle_table.values()[i].mount_id; | |
| 277 | const fan_fd = w.os.poll_fds.getEntry(mount_id).?.value_ptr.fd; | |
| 278 | posix.fanotify_mark(fan_fd, .{ | |
| 279 | .REMOVE = true, | |
| 280 | .ONLYDIR = true, | |
| 281 | }, fan_mask, path.root_dir.handle.handle, path.subPathOrDot()) catch |err| switch (err) { | |
| 282 | error.FileNotFound => {}, // Expected, harmless. | |
| 283 | else => |e| std.log.warn("unable to unwatch '{f}': {s}", .{ path, @errorName(e) }), | |
| 284 | }; | |
| 285 | ||
| 286 | w.dir_table.swapRemoveAt(i); | |
| 287 | w.os.handle_table.swapRemoveAt(i); | |
| 288 | } | |
| 289 | w.generation +%= 1; | |
| 290 | } | |
| 291 | w.dir_count = w.dir_table.count(); | |
| 292 | } | |
| 293 | ||
| 294 | fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { | |
| 295 | _ = io; | |
| 296 | const events_len = try std.posix.poll(w.os.poll_fds.values(), timeout.to_i32_ms()); | |
| 297 | if (events_len == 0) | |
| 298 | return .timeout; | |
| 299 | for (w.os.poll_fds.values()) |poll_fd| { | |
| 300 | if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and try markDirtySteps(w, gpa, poll_fd.fd)) | |
| 301 | return .dirty; | |
| 302 | } | |
| 303 | return .clean; | |
| 304 | } | |
| 305 | }, | |
| 306 | .windows => struct { | |
| 307 | const windows = std.os.windows; | |
| 308 | ||
| 309 | /// Keyed differently but indexes correspond 1:1 with `dir_table`. | |
| 310 | handle_table: std.ArrayHashMapUnmanaged(*Directory, void, Directory.TableAdapter, false), | |
| 311 | ready_dirs: std.DoublyLinkedList, | |
| 312 | ||
| 313 | const FileId = struct { | |
| 314 | volumeSerialNumber: windows.ULONG, | |
| 315 | indexNumber: windows.LARGE_INTEGER, | |
| 316 | }; | |
| 317 | ||
| 318 | const Directory = struct { | |
| 319 | reaction_set: ReactionSet, | |
| 320 | id: FileId, | |
| 321 | file: Io.File, | |
| 322 | state: enum { idle, listening, ready }, | |
| 323 | iosb: windows.IO_STATUS_BLOCK, | |
| 324 | // 64 KB is the packet size limit when monitoring over a network. | |
| 325 | // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks | |
| 326 | buffer: [64 * 1024]u8 align(@alignOf(windows.FILE.NOTIFY.INFORMATION)), | |
| 327 | ready_node: std.DoublyLinkedList.Node, | |
| 328 | ||
| 329 | /// Start listening for events, buffer field will be overwritten eventually. | |
| 330 | fn startListening(dir: *Directory, w: *Watch) !void { | |
| 331 | assert(dir.file.flags.nonblocking); | |
| 332 | assert(dir.state == .idle); | |
| 333 | switch (windows.ntdll.NtNotifyChangeDirectoryFileEx( | |
| 334 | dir.file.handle, | |
| 335 | null, | |
| 336 | &notifyApc, | |
| 337 | w, | |
| 338 | &dir.iosb, | |
| 339 | &dir.buffer, | |
| 340 | dir.buffer.len, | |
| 341 | .{ | |
| 342 | .FILE_NAME = true, | |
| 343 | .DIR_NAME = true, | |
| 344 | .SIZE = true, | |
| 345 | .LAST_WRITE = true, | |
| 346 | .CREATION = true, | |
| 347 | }, | |
| 348 | .FALSE, | |
| 349 | .Notify, | |
| 350 | )) { | |
| 351 | .SUCCESS, .PENDING => dir.state = .listening, | |
| 352 | .ILLEGAL_FUNCTION => return error.ReadDirectoryChangesUnsupported, | |
| 353 | else => |status| return windows.unexpectedStatus(status), | |
| 354 | } | |
| 355 | } | |
| 356 | ||
| 357 | fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) align(std.Io.Threaded.apc_align) callconv(.winapi) void { | |
| 358 | const w: *Watch = @ptrCast(@alignCast(apc_context)); | |
| 359 | const dir: *Directory = @fieldParentPtr("iosb", iosb); | |
| 360 | assert(iosb.u.Status != .PENDING); | |
| 361 | assert(dir.state == .listening); | |
| 362 | w.os.ready_dirs.append(&dir.ready_node); | |
| 363 | dir.state = .ready; | |
| 364 | } | |
| 365 | ||
| 366 | fn init(gpa: Allocator, path: Cache.Path) !*Directory { | |
| 367 | // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW) | |
| 368 | // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW. | |
| 369 | var dir_handle: windows.HANDLE = undefined; | |
| 370 | const root_fd = path.root_dir.handle.handle; | |
| 371 | const sub_path = path.subPathOrDot(); | |
| 372 | const sub_path_w = try Io.Threaded.sliceToPrefixedFileW(root_fd, sub_path, .{}); // TODO eliminate this call | |
| 373 | var iosb: windows.IO_STATUS_BLOCK = undefined; | |
| 374 | switch (windows.ntdll.NtCreateFile( | |
| 375 | &dir_handle, | |
| 376 | .{ | |
| 377 | .SPECIFIC = .{ .FILE_DIRECTORY = .{ | |
| 378 | .LIST = true, | |
| 379 | } }, | |
| 380 | .STANDARD = .{ .SYNCHRONIZE = true }, | |
| 381 | .GENERIC = .{ .READ = true }, | |
| 382 | }, | |
| 383 | &.{ | |
| 384 | .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, | |
| 385 | .ObjectName = @constCast(&sub_path_w.string()), | |
| 386 | }, | |
| 387 | &iosb, | |
| 388 | null, | |
| 389 | .{}, | |
| 390 | .VALID_FLAGS, | |
| 391 | .OPEN, | |
| 392 | .{ | |
| 393 | .DIRECTORY_FILE = true, | |
| 394 | .IO = .ASYNCHRONOUS, | |
| 395 | .OPEN_FOR_BACKUP_INTENT = true, | |
| 396 | }, | |
| 397 | null, | |
| 398 | 0, | |
| 399 | )) { | |
| 400 | .SUCCESS => {}, | |
| 401 | .OBJECT_NAME_INVALID => return error.BadPathName, | |
| 402 | .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, | |
| 403 | .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, | |
| 404 | .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, | |
| 405 | .NOT_A_DIRECTORY => return error.NotDir, | |
| 406 | // This can happen if the directory has 'List folder contents' permission set to 'Deny' | |
| 407 | .ACCESS_DENIED => return error.AccessDenied, | |
| 408 | .INVALID_PARAMETER => unreachable, | |
| 409 | else => |rc| return windows.unexpectedStatus(rc), | |
| 410 | } | |
| 411 | assert(dir_handle != windows.INVALID_HANDLE_VALUE); | |
| 412 | errdefer windows.CloseHandle(dir_handle); | |
| 413 | ||
| 414 | const dir_id = try getFileId(dir_handle); | |
| 415 | ||
| 416 | const dir = try gpa.create(Directory); | |
| 417 | dir.* = .{ | |
| 418 | .reaction_set = .empty, | |
| 419 | .id = dir_id, | |
| 420 | .file = .{ .handle = dir_handle, .flags = .{ .nonblocking = true } }, | |
| 421 | .state = .idle, | |
| 422 | .iosb = undefined, | |
| 423 | .buffer = undefined, | |
| 424 | .ready_node = undefined, | |
| 425 | }; | |
| 426 | return dir; | |
| 427 | } | |
| 428 | ||
| 429 | fn deinit(dir: *Directory, gpa: Allocator, w: *Watch) void { | |
| 430 | state: switch (dir.state) { | |
| 431 | .idle => {}, | |
| 432 | .listening => { | |
| 433 | var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; | |
| 434 | _ = windows.ntdll.NtCancelIoFileEx(dir.file.handle, &dir.iosb, &cancel_iosb); | |
| 435 | while (switch (dir.state) { | |
| 436 | .idle => unreachable, | |
| 437 | .listening => true, | |
| 438 | .ready => false, | |
| 439 | }) Io.Threaded.waitForApcOrAlert(); | |
| 440 | continue :state .ready; | |
| 441 | }, | |
| 442 | .ready => w.os.ready_dirs.remove(&dir.ready_node), | |
| 443 | } | |
| 444 | windows.CloseHandle(dir.file.handle); | |
| 445 | gpa.destroy(dir); | |
| 446 | } | |
| 447 | ||
| 448 | /// Useful to make `*Directory` a key in `std.ArrayHashMap`. | |
| 449 | const TableAdapter = struct { | |
| 450 | pub fn hash(_: TableAdapter, lhs_dir: *Directory) u32 { | |
| 451 | return @truncate(Hash.hash(lhs_dir.id.volumeSerialNumber, @ptrCast(&lhs_dir.id.indexNumber))); | |
| 452 | } | |
| 453 | pub fn eql(_: TableAdapter, lhs_dir: *Directory, rhs_dir: *Directory, rhs_index: usize) bool { | |
| 454 | _ = rhs_index; | |
| 455 | return lhs_dir.id.volumeSerialNumber == rhs_dir.id.volumeSerialNumber and | |
| 456 | lhs_dir.id.indexNumber == rhs_dir.id.indexNumber; | |
| 457 | } | |
| 458 | }; | |
| 459 | }; | |
| 460 | ||
| 461 | fn init(cwd_path: []const u8) !Watch { | |
| 462 | _ = cwd_path; | |
| 463 | return .{ | |
| 464 | .dir_table = .{}, | |
| 465 | .dir_count = 0, | |
| 466 | .os = switch (builtin.os.tag) { | |
| 467 | .windows => .{ | |
| 468 | .handle_table = .empty, | |
| 469 | .ready_dirs = .{}, | |
| 470 | }, | |
| 471 | else => {}, | |
| 472 | }, | |
| 473 | .generation = 0, | |
| 474 | }; | |
| 475 | } | |
| 476 | ||
| 477 | fn getFileId(handle: windows.HANDLE) !FileId { | |
| 478 | var file_id: FileId = undefined; | |
| 479 | var io_status: windows.IO_STATUS_BLOCK = undefined; | |
| 480 | var volume_info: windows.FILE.FS_VOLUME_INFORMATION = undefined; | |
| 481 | switch (windows.ntdll.NtQueryVolumeInformationFile( | |
| 482 | handle, | |
| 483 | &io_status, | |
| 484 | &volume_info, | |
| 485 | @sizeOf(windows.FILE.FS_VOLUME_INFORMATION), | |
| 486 | .Volume, | |
| 487 | )) { | |
| 488 | .SUCCESS => {}, | |
| 489 | // Buffer overflow here indicates that there is more information available than was able to be stored in the buffer | |
| 490 | // size provided. This is treated as success because the type of variable-length information that this would be relevant for | |
| 491 | // (name, volume name, etc) we don't care about. | |
| 492 | .BUFFER_OVERFLOW => {}, | |
| 493 | else => |rc| return windows.unexpectedStatus(rc), | |
| 494 | } | |
| 495 | file_id.volumeSerialNumber = volume_info.VolumeSerialNumber; | |
| 496 | var internal_info: windows.FILE.INTERNAL_INFORMATION = undefined; | |
| 497 | switch (windows.ntdll.NtQueryInformationFile( | |
| 498 | handle, | |
| 499 | &io_status, | |
| 500 | &internal_info, | |
| 501 | @sizeOf(windows.FILE.INTERNAL_INFORMATION), | |
| 502 | .Internal, | |
| 503 | )) { | |
| 504 | .SUCCESS => {}, | |
| 505 | else => |rc| return windows.unexpectedStatus(rc), | |
| 506 | } | |
| 507 | file_id.indexNumber = internal_info.IndexNumber; | |
| 508 | return file_id; | |
| 509 | } | |
| 510 | ||
| 511 | fn markDirtySteps(w: *Watch, gpa: Allocator, dir: *Directory) !bool { | |
| 512 | var any_dirty = false; | |
| 513 | const bytes_returned = dir.iosb.Information; | |
| 514 | if (bytes_returned == 0) { | |
| 515 | std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); | |
| 516 | markAllFilesDirty(w, gpa); | |
| 517 | try dir.startListening(w); | |
| 518 | return true; | |
| 519 | } | |
| 520 | var file_name_buf: [std.fs.max_path_bytes]u8 = undefined; | |
| 521 | var offset: usize = 0; | |
| 522 | while (true) { | |
| 523 | const notify: *windows.FILE.NOTIFY.INFORMATION = @ptrCast(@alignCast(&dir.buffer[offset])); | |
| 524 | const file_name = file_name_buf[0..std.unicode.wtf16LeToWtf8(&file_name_buf, notify.fileName())]; | |
| 525 | if (dir.reaction_set.getPtr(".")) |glob_set| | |
| 526 | any_dirty = markStepSetDirty(gpa, glob_set, any_dirty); | |
| 527 | if (dir.reaction_set.getPtr(file_name)) |step_set| | |
| 528 | any_dirty = markStepSetDirty(gpa, step_set, any_dirty); | |
| 529 | if (notify.NextEntryOffset == 0) | |
| 530 | break; | |
| 531 | ||
| 532 | offset += notify.NextEntryOffset; | |
| 533 | } | |
| 534 | ||
| 535 | // We call this now since at this point we have finished reading dir.buffer. | |
| 536 | try dir.startListening(w); | |
| 537 | return any_dirty; | |
| 538 | } | |
| 539 | ||
| 540 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { | |
| 541 | // Add missing marks and note persisted ones. | |
| 542 | for (steps) |step| { | |
| 543 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | |
| 544 | const dir = dir: { | |
| 545 | const gop = try w.dir_table.getOrPut(gpa, path); | |
| 546 | if (!gop.found_existing) { | |
| 547 | const dir: *Directory = try .init(gpa, path); | |
| 548 | errdefer dir.deinit(gpa, w); | |
| 549 | // `dir.id` may already be present in the table in | |
| 550 | // the case that we have multiple Cache.Path instances | |
| 551 | // that compare inequal but ultimately point to the same | |
| 552 | // directory on the file system. | |
| 553 | // In such case, we must revert adding this directory, but keep | |
| 554 | // the additions to the step set. | |
| 555 | const dh_gop = try w.os.handle_table.getOrPut(gpa, dir); | |
| 556 | if (dh_gop.found_existing) { | |
| 557 | dir.deinit(gpa, w); | |
| 558 | _ = w.dir_table.pop(); | |
| 559 | break :dir w.os.handle_table.keys()[dh_gop.index]; | |
| 560 | } else { | |
| 561 | assert(dh_gop.index == gop.index); | |
| 562 | try dir.startListening(w); | |
| 563 | break :dir dir; | |
| 564 | } | |
| 565 | } | |
| 566 | break :dir w.os.handle_table.keys()[gop.index]; | |
| 567 | }; | |
| 568 | for (files.items) |basename| { | |
| 569 | const gop = try dir.reaction_set.getOrPut(gpa, basename); | |
| 570 | if (!gop.found_existing) gop.value_ptr.* = .{}; | |
| 571 | try gop.value_ptr.put(gpa, step, w.generation); | |
| 572 | } | |
| 573 | } | |
| 574 | } | |
| 575 | ||
| 576 | { | |
| 577 | // Remove marks for files that are no longer inputs. | |
| 578 | var i: usize = 0; | |
| 579 | while (i < w.os.handle_table.entries.len) { | |
| 580 | const dir = w.os.handle_table.keys()[i]; | |
| 581 | { | |
| 582 | var step_set_i: usize = 0; | |
| 583 | while (step_set_i < dir.reaction_set.entries.len) { | |
| 584 | const step_set = &dir.reaction_set.values()[step_set_i]; | |
| 585 | var dirent_i: usize = 0; | |
| 586 | while (dirent_i < step_set.entries.len) { | |
| 587 | const generations = step_set.values(); | |
| 588 | if (generations[dirent_i] == w.generation) { | |
| 589 | dirent_i += 1; | |
| 590 | continue; | |
| 591 | } | |
| 592 | step_set.swapRemoveAt(dirent_i); | |
| 593 | } | |
| 594 | if (step_set.entries.len > 0) { | |
| 595 | step_set_i += 1; | |
| 596 | continue; | |
| 597 | } | |
| 598 | dir.reaction_set.swapRemoveAt(step_set_i); | |
| 599 | } | |
| 600 | if (dir.reaction_set.entries.len > 0) { | |
| 601 | i += 1; | |
| 602 | continue; | |
| 603 | } | |
| 604 | } | |
| 605 | ||
| 606 | w.dir_table.swapRemoveAt(i); | |
| 607 | w.os.handle_table.swapRemoveAt(i); | |
| 608 | dir.deinit(gpa, w); | |
| 609 | } | |
| 610 | w.generation +%= 1; | |
| 611 | } | |
| 612 | w.dir_count = w.dir_table.count(); | |
| 613 | } | |
| 614 | ||
| 615 | fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { | |
| 616 | for (0..2) |attempt| { | |
| 617 | while (w.os.ready_dirs.popFirst()) |ready_node| { | |
| 618 | const dir: *Directory = @fieldParentPtr("ready_node", ready_node); | |
| 619 | assert(dir.state == .ready); | |
| 620 | dir.state = .idle; | |
| 621 | switch (dir.iosb.u.Status) { | |
| 622 | .SUCCESS => return if (try markDirtySteps(w, gpa, dir)) .dirty else .clean, | |
| 623 | .PENDING => unreachable, | |
| 624 | .CANCELLED => {}, | |
| 625 | else => |status| return windows.unexpectedStatus(status), | |
| 626 | } | |
| 627 | try dir.startListening(w); | |
| 628 | } | |
| 629 | try io.checkCancel(); | |
| 630 | if (attempt == 1) return .timeout; | |
| 631 | const delay_interval: windows.LARGE_INTEGER = switch (timeout) { | |
| 632 | .none => std.math.minInt(windows.LARGE_INTEGER), | |
| 633 | .ms => |ms| -@as(windows.LARGE_INTEGER, ms) * (std.time.ns_per_ms / 100), | |
| 634 | }; | |
| 635 | _ = windows.ntdll.NtDelayExecution(.TRUE, &delay_interval); | |
| 636 | } else unreachable; | |
| 637 | } | |
| 638 | }, | |
| 639 | .dragonfly, .freebsd, .netbsd, .openbsd, .ios, .tvos, .visionos, .watchos => struct { | |
| 640 | const posix = std.posix; | |
| 641 | ||
| 642 | kq_fd: i32, | |
| 643 | /// Indexes correspond 1:1 with `dir_table`. | |
| 644 | handles: std.MultiArrayList(struct { | |
| 645 | rs: ReactionSet, | |
| 646 | /// If the corresponding dir_table Path has sub_path == "", then it | |
| 647 | /// suffices as the open directory handle, and this value will be | |
| 648 | /// -1. Otherwise, it needs to be opened in update(), and will be | |
| 649 | /// stored here. | |
| 650 | dir_fd: i32, | |
| 651 | }), | |
| 652 | ||
| 653 | const dir_open_flags: posix.O = f: { | |
| 654 | var f: posix.O = .{ | |
| 655 | .ACCMODE = .RDONLY, | |
| 656 | .NOFOLLOW = false, | |
| 657 | .DIRECTORY = true, | |
| 658 | .CLOEXEC = true, | |
| 659 | }; | |
| 660 | if (@hasField(posix.O, "EVTONLY")) f.EVTONLY = true; | |
| 661 | if (@hasField(posix.O, "PATH")) f.PATH = true; | |
| 662 | break :f f; | |
| 663 | }; | |
| 664 | ||
| 665 | const EV = std.c.EV; | |
| 666 | const NOTE = std.c.NOTE; | |
| 667 | ||
| 668 | fn init(cwd_path: []const u8) !Watch { | |
| 669 | _ = cwd_path; | |
| 670 | return .{ | |
| 671 | .dir_table = .{}, | |
| 672 | .dir_count = 0, | |
| 673 | .os = .{ | |
| 674 | .kq_fd = try Io.Kqueue.createFileDescriptor(), | |
| 675 | .handles = .empty, | |
| 676 | }, | |
| 677 | .generation = 0, | |
| 678 | }; | |
| 679 | } | |
| 680 | ||
| 681 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { | |
| 682 | const handles = &w.os.handles; | |
| 683 | for (steps) |step| { | |
| 684 | for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { | |
| 685 | const reaction_set = rs: { | |
| 686 | const gop = try w.dir_table.getOrPut(gpa, path); | |
| 687 | if (!gop.found_existing) { | |
| 688 | const skip_open_dir = path.sub_path.len == 0; | |
| 689 | const dir_fd = if (skip_open_dir) | |
| 690 | path.root_dir.handle.handle | |
| 691 | else | |
| 692 | posix.openat(path.root_dir.handle.handle, path.sub_path, dir_open_flags, 0) catch |err| { | |
| 693 | fatal("failed to open directory {f}: {t}", .{ path, err }); | |
| 694 | }; | |
| 695 | // Empirically the dir has to stay open or else no events are triggered. | |
| 696 | errdefer if (!skip_open_dir) std.Io.Threaded.closeFd(dir_fd); | |
| 697 | const changes = [1]posix.Kevent{.{ | |
| 698 | .ident = @bitCast(@as(isize, dir_fd)), | |
| 699 | .filter = std.c.EVFILT.VNODE, | |
| 700 | .flags = EV.ADD | EV.ENABLE | EV.CLEAR, | |
| 701 | .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE, | |
| 702 | .data = 0, | |
| 703 | .udata = gop.index, | |
| 704 | }}; | |
| 705 | _ = try Io.Kqueue.kevent(w.os.kq_fd, &changes, &.{}, null); | |
| 706 | assert(handles.len == gop.index); | |
| 707 | try handles.append(gpa, .{ | |
| 708 | .rs = .{}, | |
| 709 | .dir_fd = if (skip_open_dir) -1 else dir_fd, | |
| 710 | }); | |
| 711 | } | |
| 712 | ||
| 713 | break :rs &handles.items(.rs)[gop.index]; | |
| 714 | }; | |
| 715 | for (files.items) |basename| { | |
| 716 | const gop = try reaction_set.getOrPut(gpa, basename); | |
| 717 | if (!gop.found_existing) gop.value_ptr.* = .{}; | |
| 718 | try gop.value_ptr.put(gpa, step, w.generation); | |
| 719 | } | |
| 720 | } | |
| 721 | } | |
| 722 | ||
| 723 | { | |
| 724 | // Remove marks for files that are no longer inputs. | |
| 725 | var i: usize = 0; | |
| 726 | while (i < handles.len) { | |
| 727 | { | |
| 728 | const reaction_set = &handles.items(.rs)[i]; | |
| 729 | var step_set_i: usize = 0; | |
| 730 | while (step_set_i < reaction_set.entries.len) { | |
| 731 | const step_set = &reaction_set.values()[step_set_i]; | |
| 732 | var dirent_i: usize = 0; | |
| 733 | while (dirent_i < step_set.entries.len) { | |
| 734 | const generations = step_set.values(); | |
| 735 | if (generations[dirent_i] == w.generation) { | |
| 736 | dirent_i += 1; | |
| 737 | continue; | |
| 738 | } | |
| 739 | step_set.swapRemoveAt(dirent_i); | |
| 740 | } | |
| 741 | if (step_set.entries.len > 0) { | |
| 742 | step_set_i += 1; | |
| 743 | continue; | |
| 744 | } | |
| 745 | reaction_set.swapRemoveAt(step_set_i); | |
| 746 | } | |
| 747 | if (reaction_set.entries.len > 0) { | |
| 748 | i += 1; | |
| 749 | continue; | |
| 750 | } | |
| 751 | } | |
| 752 | ||
| 753 | // If the sub_path == "" then this patch has already the | |
| 754 | // dir fd that we need to use as the ident to remove the | |
| 755 | // event. If it was opened above with openat() then we need | |
| 756 | // to access that data via the dir_fd field. | |
| 757 | const path = w.dir_table.keys()[i]; | |
| 758 | const dir_fd = if (path.sub_path.len == 0) | |
| 759 | path.root_dir.handle.handle | |
| 760 | else | |
| 761 | handles.items(.dir_fd)[i]; | |
| 762 | assert(dir_fd != -1); | |
| 763 | ||
| 764 | // The changelist also needs to update the udata field of the last | |
| 765 | // event, since we are doing a swap remove, and we store the dir_table | |
| 766 | // index in the udata field. | |
| 767 | const last_dir_fd = fd: { | |
| 768 | const last_path = w.dir_table.keys()[handles.len - 1]; | |
| 769 | const last_dir_fd = if (last_path.sub_path.len == 0) | |
| 770 | last_path.root_dir.handle.handle | |
| 771 | else | |
| 772 | handles.items(.dir_fd)[handles.len - 1]; | |
| 773 | assert(last_dir_fd != -1); | |
| 774 | break :fd last_dir_fd; | |
| 775 | }; | |
| 776 | const changes = [_]posix.Kevent{ | |
| 777 | .{ | |
| 778 | .ident = @bitCast(@as(isize, dir_fd)), | |
| 779 | .filter = std.c.EVFILT.VNODE, | |
| 780 | .flags = EV.DELETE, | |
| 781 | .fflags = 0, | |
| 782 | .data = 0, | |
| 783 | .udata = i, | |
| 784 | }, | |
| 785 | .{ | |
| 786 | .ident = @bitCast(@as(isize, last_dir_fd)), | |
| 787 | .filter = std.c.EVFILT.VNODE, | |
| 788 | .flags = EV.ADD, | |
| 789 | .fflags = NOTE.DELETE | NOTE.WRITE | NOTE.RENAME | NOTE.REVOKE, | |
| 790 | .data = 0, | |
| 791 | .udata = i, | |
| 792 | }, | |
| 793 | }; | |
| 794 | const filtered_changes = if (i == handles.len - 1) changes[0..1] else &changes; | |
| 795 | _ = try Io.Kqueue.kevent(w.os.kq_fd, filtered_changes, &.{}, null); | |
| 796 | if (path.sub_path.len != 0) std.Io.Threaded.closeFd(dir_fd); | |
| 797 | ||
| 798 | w.dir_table.swapRemoveAt(i); | |
| 799 | handles.swapRemove(i); | |
| 800 | } | |
| 801 | w.generation +%= 1; | |
| 802 | } | |
| 803 | w.dir_count = w.dir_table.count(); | |
| 804 | } | |
| 805 | ||
| 806 | fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { | |
| 807 | _ = io; | |
| 808 | var timespec_buffer: posix.timespec = undefined; | |
| 809 | var event_buffer: [100]posix.Kevent = undefined; | |
| 810 | var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(&timespec_buffer)); | |
| 811 | if (n == 0) return .timeout; | |
| 812 | const reaction_sets = w.os.handles.items(.rs); | |
| 813 | var any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], false); | |
| 814 | timespec_buffer = .{ .sec = 0, .nsec = 0 }; | |
| 815 | while (n == event_buffer.len) { | |
| 816 | n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, &timespec_buffer); | |
| 817 | if (n == 0) break; | |
| 818 | any_dirty = markDirtySteps(gpa, reaction_sets, event_buffer[0..n], any_dirty); | |
| 819 | } | |
| 820 | return if (any_dirty) .dirty else .clean; | |
| 821 | } | |
| 822 | ||
| 823 | fn markDirtySteps( | |
| 824 | gpa: Allocator, | |
| 825 | reaction_sets: []ReactionSet, | |
| 826 | events: []const std.c.Kevent, | |
| 827 | start_any_dirty: bool, | |
| 828 | ) bool { | |
| 829 | var any_dirty = start_any_dirty; | |
| 830 | for (events) |event| { | |
| 831 | const index: usize = @intCast(event.udata); | |
| 832 | const reaction_set = &reaction_sets[index]; | |
| 833 | // If we knew the basename of the changed file, here we would | |
| 834 | // mark only the step set dirty, and possibly the glob set: | |
| 835 | //if (reaction_set.getPtr(".")) |glob_set| | |
| 836 | // any_dirty = markStepSetDirty(gpa, glob_set, any_dirty); | |
| 837 | //if (reaction_set.getPtr(file_name)) |step_set| | |
| 838 | // any_dirty = markStepSetDirty(gpa, step_set, any_dirty); | |
| 839 | // However we don't know the file name so just mark all the | |
| 840 | // sets dirty for this directory. | |
| 841 | for (reaction_set.values()) |*step_set| { | |
| 842 | any_dirty = markStepSetDirty(gpa, step_set, any_dirty); | |
| 843 | } | |
| 844 | } | |
| 845 | return any_dirty; | |
| 846 | } | |
| 847 | }, | |
| 848 | .macos => struct { | |
| 849 | fse: FsEvents, | |
| 850 | ||
| 851 | fn init(cwd_path: []const u8) !Watch { | |
| 852 | return .{ | |
| 853 | .os = .{ .fse = try .init(cwd_path) }, | |
| 854 | .dir_count = 0, | |
| 855 | .dir_table = undefined, | |
| 856 | .generation = undefined, | |
| 857 | }; | |
| 858 | } | |
| 859 | fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { | |
| 860 | try w.os.fse.setPaths(gpa, steps); | |
| 861 | w.dir_count = w.os.fse.watch_roots.len; | |
| 862 | } | |
| 863 | fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { | |
| 864 | _ = io; | |
| 865 | return w.os.fse.wait(gpa, switch (timeout) { | |
| 866 | .none => null, | |
| 867 | .ms => |ms| @as(u64, ms) * std.time.ns_per_ms, | |
| 868 | }); | |
| 869 | } | |
| 870 | }, | |
| 871 | else => void, | |
| 872 | }; | |
| 873 | ||
| 874 | pub fn init(cwd_path: []const u8) !Watch { | |
| 875 | return Os.init(cwd_path); | |
| 876 | } | |
| 877 | ||
| 878 | pub const Match = struct { | |
| 879 | /// Relative to the watched directory, the file path that triggers this | |
| 880 | /// match. | |
| 881 | basename: []const u8, | |
| 882 | /// The step to re-run when file corresponding to `basename` is changed. | |
| 883 | step: *Step, | |
| 884 | ||
| 885 | pub const Context = struct { | |
| 886 | pub fn hash(self: Context, a: Match) u32 { | |
| 887 | _ = self; | |
| 888 | var hasher = Hash.init(0); | |
| 889 | std.hash.autoHash(&hasher, a.step); | |
| 890 | hasher.update(a.basename); | |
| 891 | return @truncate(hasher.final()); | |
| 892 | } | |
| 893 | pub fn eql(self: Context, a: Match, b: Match, b_index: usize) bool { | |
| 894 | _ = self; | |
| 895 | _ = b_index; | |
| 896 | return a.step == b.step and std.mem.eql(u8, a.basename, b.basename); | |
| 897 | } | |
| 898 | }; | |
| 899 | }; | |
| 900 | ||
| 901 | fn markAllFilesDirty(w: *Watch, gpa: Allocator) void { | |
| 902 | for (switch (builtin.os.tag) { | |
| 903 | .windows => w.os.handle_table.keys(), | |
| 904 | else => w.os.handle_table.values(), | |
| 905 | }) |item| { | |
| 906 | const reaction_set = switch (builtin.os.tag) { | |
| 907 | .linux, .windows => item.reaction_set, | |
| 908 | else => item, | |
| 909 | }; | |
| 910 | for (reaction_set.values()) |step_set| { | |
| 911 | for (step_set.keys()) |step| { | |
| 912 | _ = step.invalidateResult(gpa); | |
| 913 | } | |
| 914 | } | |
| 915 | } | |
| 916 | } | |
| 917 | ||
| 918 | fn markStepSetDirty(gpa: Allocator, step_set: *StepSet, any_dirty: bool) bool { | |
| 919 | var this_any_dirty = false; | |
| 920 | for (step_set.keys()) |step| { | |
| 921 | if (step.invalidateResult(gpa)) this_any_dirty = true; | |
| 922 | } | |
| 923 | return any_dirty or this_any_dirty; | |
| 924 | } | |
| 925 | ||
| 926 | pub fn update(w: *Watch, gpa: Allocator, steps: []const *Step) !void { | |
| 927 | return Os.update(w, gpa, steps); | |
| 928 | } | |
| 929 | ||
| 930 | pub const Timeout = union(enum) { | |
| 931 | none, | |
| 932 | ms: u16, | |
| 933 | ||
| 934 | pub fn to_i32_ms(t: Timeout) i32 { | |
| 935 | return switch (t) { | |
| 936 | .none => -1, | |
| 937 | .ms => |ms| ms, | |
| 938 | }; | |
| 939 | } | |
| 940 | ||
| 941 | pub fn toTimespec(t: Timeout, buf: *std.posix.timespec) ?*std.posix.timespec { | |
| 942 | return switch (t) { | |
| 943 | .none => null, | |
| 944 | .ms => |ms_u16| { | |
| 945 | const ms: isize = ms_u16; | |
| 946 | buf.* = .{ | |
| 947 | .sec = @divTrunc(ms, std.time.ms_per_s), | |
| 948 | .nsec = @rem(ms, std.time.ms_per_s) * std.time.ns_per_ms, | |
| 949 | }; | |
| 950 | return buf; | |
| 951 | }, | |
| 952 | }; | |
| 953 | } | |
| 954 | }; | |
| 955 | ||
| 956 | pub const WaitResult = enum { | |
| 957 | timeout, | |
| 958 | /// File system watching triggered on files that were marked as inputs to at least one Step. | |
| 959 | /// Relevant steps have been marked dirty. | |
| 960 | dirty, | |
| 961 | /// File system watching triggered but none of the events were relevant to | |
| 962 | /// what we are listening to. There is nothing to do. | |
| 963 | clean, | |
| 964 | }; | |
| 965 | ||
| 966 | pub fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { | |
| 967 | return Os.wait(w, gpa, io, timeout); | |
| 968 | } |
lib/std/Build/Watch/FsEvents.zig deleted-479| ... | ... | @@ -1,479 +0,0 @@ |
| 1 | //! An implementation of file-system watching based on the `FSEventStream` API in macOS. | |
| 2 | //! While macOS supports kqueue, it does not allow detecting changes to files without | |
| 3 | //! placing watches on each individual file, meaning FD limits are reached incredibly | |
| 4 | //! quickly. The File System Events API works differently: it implements *recursive* | |
| 5 | //! directory watches, managed by a system service. Rather than being in libc, the API is | |
| 6 | //! exposed by the CoreServices framework. To avoid a compile dependency on the framework | |
| 7 | //! bundle, we dynamically load CoreServices with `std.DynLib`. | |
| 8 | //! | |
| 9 | //! While the logic in this file *is* specialized to `std.Build.Watch`, efforts have been | |
| 10 | //! made to keep that specialization to a minimum. Other use cases could be served with | |
| 11 | //! relatively minimal modifications to the `watch_paths` field and its usages (in | |
| 12 | //! particular the `setPaths` function). We avoid using the global GCD dispatch queue in | |
| 13 | //! favour of creating our own and synchronizing with an explicit semaphore, meaning this | |
| 14 | //! logic is thread-safe and does not affect process-global state. | |
| 15 | //! | |
| 16 | //! In theory, this API is quite good at avoiding filesystem race conditions. In practice, | |
| 17 | //! the logic that would avoid them is currently disabled, because the build system kind | |
| 18 | //! of relies on them at the time of writing to avoid redundant work -- see the comment at | |
| 19 | //! the top of `wait` for details. | |
| 20 | ||
| 21 | const enable_debug_logs = false; | |
| 22 | ||
| 23 | core_services: std.DynLib, | |
| 24 | resolved_symbols: ResolvedSymbols, | |
| 25 | ||
| 26 | paths_arena: std.heap.ArenaAllocator.State, | |
| 27 | /// The roots of the recursive watches. FSEvents has relatively small limits on the number | |
| 28 | /// of watched paths, so this slice must not be too long. The paths themselves are allocated | |
| 29 | /// into `paths_arena`, but this slice is allocated into the GPA. | |
| 30 | watch_roots: [][:0]const u8, | |
| 31 | /// All of the paths being watched. Value is the set of steps which depend on the file/directory. | |
| 32 | /// Keys and values are in `paths_arena`, but this map is allocated into the GPA. | |
| 33 | watch_paths: std.StringArrayHashMapUnmanaged([]const *std.Build.Step), | |
| 34 | ||
| 35 | /// The semaphore we use to block the thread calling `wait` until the callback determines a relevant | |
| 36 | /// event has occurred. This is retained across `wait` calls for simplicity and efficiency. | |
| 37 | waiting_semaphore: dispatch.semaphore_t, | |
| 38 | /// This dispatch queue is created by us and executes serially. It exists exclusively to trigger the | |
| 39 | /// callbacks of the FSEventStream we create. This is not in use outside of `wait`, but is retained | |
| 40 | /// across `wait` calls for simplicity and efficiency. | |
| 41 | dispatch_queue: dispatch.queue_t, | |
| 42 | /// In theory, this field avoids race conditions. In practice, it is essentially unused at the time | |
| 43 | /// of writing. See the comment at the start of `wait` for details. | |
| 44 | since_event: FSEventStreamEventId, | |
| 45 | ||
| 46 | cwd_path: []const u8, | |
| 47 | ||
| 48 | /// All of the symbols we pull from the `dlopen`ed CoreServices framework. If any of these symbols | |
| 49 | /// is not present, `init` will close the framework and return an error. | |
| 50 | const ResolvedSymbols = struct { | |
| 51 | FSEventStreamCreate: *const fn ( | |
| 52 | allocator: CFAllocatorRef, | |
| 53 | callback: FSEventStreamCallback, | |
| 54 | ctx: ?*const FSEventStreamContext, | |
| 55 | paths_to_watch: CFArrayRef, | |
| 56 | since_when: FSEventStreamEventId, | |
| 57 | latency: CFTimeInterval, | |
| 58 | flags: FSEventStreamCreateFlags, | |
| 59 | ) callconv(.c) FSEventStreamRef, | |
| 60 | FSEventStreamSetDispatchQueue: *const fn (stream: FSEventStreamRef, queue: dispatch.queue_t) callconv(.c) void, | |
| 61 | FSEventStreamStart: *const fn (stream: FSEventStreamRef) callconv(.c) bool, | |
| 62 | FSEventStreamStop: *const fn (stream: FSEventStreamRef) callconv(.c) void, | |
| 63 | FSEventStreamInvalidate: *const fn (stream: FSEventStreamRef) callconv(.c) void, | |
| 64 | FSEventStreamRelease: *const fn (stream: FSEventStreamRef) callconv(.c) void, | |
| 65 | FSEventStreamGetLatestEventId: *const fn (stream: ConstFSEventStreamRef) callconv(.c) FSEventStreamEventId, | |
| 66 | FSEventsGetCurrentEventId: *const fn () callconv(.c) FSEventStreamEventId, | |
| 67 | CFRelease: *const fn (cf: *const anyopaque) callconv(.c) void, | |
| 68 | CFArrayCreate: *const fn ( | |
| 69 | allocator: CFAllocatorRef, | |
| 70 | values: [*]const usize, | |
| 71 | num_values: CFIndex, | |
| 72 | call_backs: ?*const CFArrayCallBacks, | |
| 73 | ) callconv(.c) CFArrayRef, | |
| 74 | CFStringCreateWithCString: *const fn ( | |
| 75 | alloc: CFAllocatorRef, | |
| 76 | c_str: [*:0]const u8, | |
| 77 | encoding: CFStringEncoding, | |
| 78 | ) callconv(.c) CFStringRef, | |
| 79 | CFAllocatorCreate: *const fn (allocator: CFAllocatorRef, context: *const CFAllocatorContext) callconv(.c) CFAllocatorRef, | |
| 80 | kCFAllocatorUseContext: *const CFAllocatorRef, | |
| 81 | }; | |
| 82 | ||
| 83 | pub fn init(cwd_path: []const u8) error{ OpenFrameworkFailed, MissingCoreServicesSymbol, SystemResources }!FsEvents { | |
| 84 | var core_services = std.DynLib.open("/System/Library/Frameworks/CoreServices.framework/CoreServices") catch | |
| 85 | return error.OpenFrameworkFailed; | |
| 86 | errdefer core_services.close(); | |
| 87 | ||
| 88 | var resolved_symbols: ResolvedSymbols = undefined; | |
| 89 | inline for (@typeInfo(ResolvedSymbols).@"struct".fields) |f| { | |
| 90 | @field(resolved_symbols, f.name) = core_services.lookup(f.type, f.name) orelse return error.MissingCoreServicesSymbol; | |
| 91 | } | |
| 92 | ||
| 93 | return .{ | |
| 94 | .core_services = core_services, | |
| 95 | .resolved_symbols = resolved_symbols, | |
| 96 | .paths_arena = .{}, | |
| 97 | .watch_roots = &.{}, | |
| 98 | .watch_paths = .empty, | |
| 99 | .waiting_semaphore = dispatch.semaphore_create(0) orelse return error.SystemResources, | |
| 100 | .dispatch_queue = dispatch.queue_create("zig-watch", .SERIAL()) orelse return error.SystemResources, | |
| 101 | // Not `.since_now`, because this means we can init `FsEvents` *before* we do work in order | |
| 102 | // to notice any changes which happened during said work. | |
| 103 | .since_event = resolved_symbols.FSEventsGetCurrentEventId(), | |
| 104 | .cwd_path = cwd_path, | |
| 105 | }; | |
| 106 | } | |
| 107 | ||
| 108 | pub fn deinit(fse: *FsEvents, gpa: Allocator, io: Io) void { | |
| 109 | fse.waiting_semaphore.as_object().release(); | |
| 110 | fse.dispatch_queue.as_object().release(); | |
| 111 | fse.core_services.close(io); | |
| 112 | ||
| 113 | gpa.free(fse.watch_roots); | |
| 114 | fse.watch_paths.deinit(gpa); | |
| 115 | { | |
| 116 | var paths_arena = fse.paths_arena.promote(gpa); | |
| 117 | paths_arena.deinit(); | |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | pub fn setPaths(fse: *FsEvents, gpa: Allocator, steps: []const *std.Build.Step) !void { | |
| 122 | var paths_arena_instance = fse.paths_arena.promote(gpa); | |
| 123 | defer fse.paths_arena = paths_arena_instance.state; | |
| 124 | const paths_arena = paths_arena_instance.allocator(); | |
| 125 | ||
| 126 | var need_dirs: std.StringArrayHashMapUnmanaged(void) = .empty; | |
| 127 | defer need_dirs.deinit(gpa); | |
| 128 | ||
| 129 | fse.watch_paths.clearRetainingCapacity(); | |
| 130 | ||
| 131 | // We take `step` by pointer for a slight memory optimization in a moment. | |
| 132 | for (steps) |*step| { | |
| 133 | for (step.*.inputs.table.keys(), step.*.inputs.table.values()) |path, *files| { | |
| 134 | const resolved_dir = try std.fs.path.resolvePosix(paths_arena, &.{ | |
| 135 | fse.cwd_path, path.root_dir.path orelse ".", path.sub_path, | |
| 136 | }); | |
| 137 | try need_dirs.put(gpa, resolved_dir, {}); | |
| 138 | for (files.items) |file_name| { | |
| 139 | const watch_path = if (std.mem.eql(u8, file_name, ".")) | |
| 140 | resolved_dir | |
| 141 | else | |
| 142 | try std.fs.path.join(paths_arena, &.{ resolved_dir, file_name }); | |
| 143 | const gop = try fse.watch_paths.getOrPut(gpa, watch_path); | |
| 144 | if (gop.found_existing) { | |
| 145 | const old_steps = gop.value_ptr.*; | |
| 146 | const new_steps = try paths_arena.alloc(*std.Build.Step, old_steps.len + 1); | |
| 147 | @memcpy(new_steps[0..old_steps.len], old_steps); | |
| 148 | new_steps[old_steps.len] = step.*; | |
| 149 | gop.value_ptr.* = new_steps; | |
| 150 | } else { | |
| 151 | // This is why we captured `step` by pointer! We can avoid allocating a slice of one | |
| 152 | // step in the arena in the common case where a file is referenced by only one step. | |
| 153 | gop.value_ptr.* = step[0..1]; | |
| 154 | } | |
| 155 | } | |
| 156 | } | |
| 157 | } | |
| 158 | ||
| 159 | { | |
| 160 | // There's no point looking at directories inside other ones (e.g. "/foo" and "/foo/bar"). | |
| 161 | // To eliminate these, we'll re-add directories in order of path length with a redundancy check. | |
| 162 | const old_dirs = try gpa.dupe([]const u8, need_dirs.keys()); | |
| 163 | defer gpa.free(old_dirs); | |
| 164 | std.mem.sort([]const u8, old_dirs, {}, struct { | |
| 165 | fn lessThan(ctx: void, a: []const u8, b: []const u8) bool { | |
| 166 | ctx; | |
| 167 | return std.mem.lessThan(u8, a, b); | |
| 168 | } | |
| 169 | }.lessThan); | |
| 170 | need_dirs.clearRetainingCapacity(); | |
| 171 | for (old_dirs) |dir_path| { | |
| 172 | var it: std.fs.path.ComponentIterator(.posix, u8) = .init(dir_path); | |
| 173 | while (it.next()) |component| { | |
| 174 | if (need_dirs.contains(component.path)) { | |
| 175 | // this path is '/foo/bar/qux', but '/foo' or '/foo/bar' was already added | |
| 176 | break; | |
| 177 | } | |
| 178 | } else { | |
| 179 | need_dirs.putAssumeCapacityNoClobber(dir_path, {}); | |
| 180 | } | |
| 181 | } | |
| 182 | } | |
| 183 | ||
| 184 | // `need_dirs` is now a set of directories to watch with no redundancy. In practice, this is very | |
| 185 | // likely to have reduced it to a quite small set (e.g. it'll typically coalesce a full `src/` | |
| 186 | // directory into one entry). However, the FSEventStream API has a fairly low undocumented limit | |
| 187 | // on total watches (supposedly 4096), so we should handle the case where we exceed it. To be | |
| 188 | // safe, because this API can be a little unpredictable, we'll cap ourselves a little *below* | |
| 189 | // that known limit. | |
| 190 | if (need_dirs.count() > 2048) { | |
| 191 | // Fallback: watch the whole filesystem. This is excessive, but... it *works* :P | |
| 192 | if (enable_debug_logs) watch_log.debug("too many dirs; recursively watching root", .{}); | |
| 193 | fse.watch_roots = try gpa.realloc(fse.watch_roots, 1); | |
| 194 | fse.watch_roots[0] = "/"; | |
| 195 | } else { | |
| 196 | fse.watch_roots = try gpa.realloc(fse.watch_roots, need_dirs.count()); | |
| 197 | for (fse.watch_roots, need_dirs.keys()) |*out, in| { | |
| 198 | out.* = try paths_arena.dupeSentinel(u8, in, 0); | |
| 199 | } | |
| 200 | } | |
| 201 | if (enable_debug_logs) { | |
| 202 | watch_log.debug("watching {d} paths using {d} recursive watches:", .{ fse.watch_paths.count(), fse.watch_roots.len }); | |
| 203 | for (fse.watch_roots) |dir_path| { | |
| 204 | watch_log.debug("- '{s}'", .{dir_path}); | |
| 205 | } | |
| 206 | } | |
| 207 | } | |
| 208 | ||
| 209 | pub fn wait(fse: *FsEvents, gpa: Allocator, timeout_ns: ?u64) error{ OutOfMemory, StartFailed }!std.Build.Watch.WaitResult { | |
| 210 | if (fse.watch_roots.len == 0) @panic("nothing to watch"); | |
| 211 | ||
| 212 | const rs = fse.resolved_symbols; | |
| 213 | ||
| 214 | // At the time of writing, using `since_event` in the obvious way causes redundant rebuilds | |
| 215 | // to occur, because one step modifies a file which is an input to another step. The solution | |
| 216 | // to this problem will probably be either: | |
| 217 | // | |
| 218 | // a) Don't include the output of one step as a watch input of another; only mark external | |
| 219 | // files as watch inputs. Or... | |
| 220 | // | |
| 221 | // b) Note the current event ID when a step begins, and disregard events preceding that ID | |
| 222 | // when considering whether to dirty that step in `eventCallback`. | |
| 223 | // | |
| 224 | // For now, to avoid the redundant rebuilds, we bypass this `since_event` mechanism. This does | |
| 225 | // introduce race conditions, but the other `std.Build.Watch` implementations suffer from those | |
| 226 | // too at the time of writing, so this is kind of expected. | |
| 227 | fse.since_event = .since_now; | |
| 228 | ||
| 229 | const cf_allocator = rs.CFAllocatorCreate(rs.kCFAllocatorUseContext.*, &.{ | |
| 230 | .version = 0, | |
| 231 | .info = @constCast(&gpa), | |
| 232 | .retain = null, | |
| 233 | .release = null, | |
| 234 | .copy_description = null, | |
| 235 | .allocate = &cf_alloc_callbacks.allocate, | |
| 236 | .reallocate = &cf_alloc_callbacks.reallocate, | |
| 237 | .deallocate = &cf_alloc_callbacks.deallocate, | |
| 238 | .preferred_size = null, | |
| 239 | }) orelse return error.OutOfMemory; | |
| 240 | defer rs.CFRelease(cf_allocator); | |
| 241 | ||
| 242 | const cf_paths = try gpa.alloc(?CFStringRef, fse.watch_roots.len); | |
| 243 | @memset(cf_paths, null); | |
| 244 | defer { | |
| 245 | for (cf_paths) |o| if (o) |p| rs.CFRelease(p); | |
| 246 | gpa.free(cf_paths); | |
| 247 | } | |
| 248 | for (fse.watch_roots, cf_paths) |raw_path, *cf_path| { | |
| 249 | cf_path.* = rs.CFStringCreateWithCString(cf_allocator, raw_path, .utf8); | |
| 250 | } | |
| 251 | const cf_paths_array = rs.CFArrayCreate(cf_allocator, @ptrCast(cf_paths), @intCast(cf_paths.len), null); | |
| 252 | defer rs.CFRelease(cf_paths_array); | |
| 253 | ||
| 254 | const callback_ctx: EventCallbackCtx = .{ | |
| 255 | .fse = fse, | |
| 256 | .gpa = gpa, | |
| 257 | }; | |
| 258 | const event_stream = rs.FSEventStreamCreate( | |
| 259 | null, | |
| 260 | &eventCallback, | |
| 261 | &.{ | |
| 262 | .version = 0, | |
| 263 | .info = @constCast(&callback_ctx), | |
| 264 | .retain = null, | |
| 265 | .release = null, | |
| 266 | .copy_description = null, | |
| 267 | }, | |
| 268 | cf_paths_array, | |
| 269 | fse.since_event, | |
| 270 | 0.05, // 0.05s latency; higher values increase efficiency by coalescing more events | |
| 271 | .{ .watch_root = true, .file_events = true }, | |
| 272 | ); | |
| 273 | defer rs.FSEventStreamRelease(event_stream); | |
| 274 | rs.FSEventStreamSetDispatchQueue(event_stream, fse.dispatch_queue); | |
| 275 | defer rs.FSEventStreamInvalidate(event_stream); | |
| 276 | if (!rs.FSEventStreamStart(event_stream)) return error.StartFailed; | |
| 277 | defer rs.FSEventStreamStop(event_stream); | |
| 278 | const result = fse.waiting_semaphore.wait(timeout: { | |
| 279 | const ns = timeout_ns orelse break :timeout .FOREVER; | |
| 280 | break :timeout .time(.NOW, @intCast(ns)); | |
| 281 | }); | |
| 282 | return switch (result) { | |
| 283 | 0 => .dirty, | |
| 284 | else => .timeout, | |
| 285 | }; | |
| 286 | } | |
| 287 | ||
| 288 | const cf_alloc_callbacks = struct { | |
| 289 | const log = std.log.scoped(.cf_alloc); | |
| 290 | fn allocate(size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque { | |
| 291 | if (enable_debug_logs) log.debug("allocate {d}", .{size}); | |
| 292 | _ = hint; | |
| 293 | const gpa: *const Allocator = @ptrCast(@alignCast(info)); | |
| 294 | const mem = gpa.alignedAlloc(u8, .of(usize), @intCast(size + @sizeOf(usize))) catch return null; | |
| 295 | const metadata: *usize = @ptrCast(mem); | |
| 296 | metadata.* = @intCast(size); | |
| 297 | return mem[@sizeOf(usize)..].ptr; | |
| 298 | } | |
| 299 | fn reallocate(ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque { | |
| 300 | if (enable_debug_logs) log.debug("reallocate @{*} {d}", .{ ptr, new_size }); | |
| 301 | _ = hint; | |
| 302 | if (ptr == null or new_size == 0) return null; // not a bug: documentation explicitly states that realloc on NULL should return NULL | |
| 303 | const gpa: *const Allocator = @ptrCast(@alignCast(info)); | |
| 304 | const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize)); | |
| 305 | const old_size = @as(*const usize, @ptrCast(old_base)).*; | |
| 306 | const old_mem = old_base[0 .. old_size + @sizeOf(usize)]; | |
| 307 | const new_mem = gpa.realloc(old_mem, @intCast(new_size + @sizeOf(usize))) catch return null; | |
| 308 | const metadata: *usize = @ptrCast(new_mem); | |
| 309 | metadata.* = @intCast(new_size); | |
| 310 | return new_mem[@sizeOf(usize)..].ptr; | |
| 311 | } | |
| 312 | fn deallocate(ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void { | |
| 313 | if (enable_debug_logs) log.debug("deallocate @{*}", .{ptr}); | |
| 314 | const gpa: *const Allocator = @ptrCast(@alignCast(info)); | |
| 315 | const old_base: [*]align(@alignOf(usize)) u8 = @alignCast(@as([*]u8, @ptrCast(ptr)) - @sizeOf(usize)); | |
| 316 | const old_size = @as(*const usize, @ptrCast(old_base)).*; | |
| 317 | const old_mem = old_base[0 .. old_size + @sizeOf(usize)]; | |
| 318 | gpa.free(old_mem); | |
| 319 | } | |
| 320 | }; | |
| 321 | ||
| 322 | const EventCallbackCtx = struct { | |
| 323 | fse: *FsEvents, | |
| 324 | gpa: Allocator, | |
| 325 | }; | |
| 326 | ||
| 327 | fn eventCallback( | |
| 328 | stream: ConstFSEventStreamRef, | |
| 329 | client_callback_info: ?*anyopaque, | |
| 330 | num_events: usize, | |
| 331 | events_paths_ptr: *anyopaque, | |
| 332 | events_flags_ptr: [*]const FSEventStreamEventFlags, | |
| 333 | events_ids_ptr: [*]const FSEventStreamEventId, | |
| 334 | ) callconv(.c) void { | |
| 335 | const ctx: *const EventCallbackCtx = @ptrCast(@alignCast(client_callback_info)); | |
| 336 | const fse = ctx.fse; | |
| 337 | const gpa = ctx.gpa; | |
| 338 | const rs = fse.resolved_symbols; | |
| 339 | const events_paths_ptr_casted: [*]const [*:0]const u8 = @ptrCast(@alignCast(events_paths_ptr)); | |
| 340 | const events_paths = events_paths_ptr_casted[0..num_events]; | |
| 341 | const events_ids = events_ids_ptr[0..num_events]; | |
| 342 | const events_flags = events_flags_ptr[0..num_events]; | |
| 343 | var any_dirty = false; | |
| 344 | for (events_paths, events_ids, events_flags) |event_path_nts, event_id, event_flags| { | |
| 345 | _ = event_id; | |
| 346 | if (event_flags.history_done) continue; // sentinel | |
| 347 | const event_path = std.mem.span(event_path_nts); | |
| 348 | switch (event_flags.must_scan_sub_dirs) { | |
| 349 | false => { | |
| 350 | if (fse.watch_paths.get(event_path)) |steps| { | |
| 351 | assert(steps.len > 0); | |
| 352 | for (steps) |s| { | |
| 353 | if (s.invalidateResult(gpa)) any_dirty = true; | |
| 354 | } | |
| 355 | } | |
| 356 | if (std.fs.path.dirname(event_path)) |event_dirname| { | |
| 357 | // Modifying '/foo/bar' triggers the watch on '/foo'. | |
| 358 | if (fse.watch_paths.get(event_dirname)) |steps| { | |
| 359 | assert(steps.len > 0); | |
| 360 | for (steps) |s| { | |
| 361 | if (s.invalidateResult(gpa)) any_dirty = true; | |
| 362 | } | |
| 363 | } | |
| 364 | } | |
| 365 | }, | |
| 366 | true => { | |
| 367 | // This is unlikely, but can occasionally happen when bottlenecked: events have been | |
| 368 | // coalesced into one. We want to see if any of these events are actually relevant | |
| 369 | // to us. The only way we can reasonably do that in this rare edge case is iterate | |
| 370 | // the watch paths and see if any is under this directory. That's acceptable because | |
| 371 | // we would otherwise kick off a rebuild which would be clearing those paths anyway. | |
| 372 | const changed_path = std.fs.path.dirname(event_path) orelse event_path; | |
| 373 | for (fse.watch_paths.keys(), fse.watch_paths.values()) |watching_path, steps| { | |
| 374 | if (dirStartsWith(watching_path, changed_path)) { | |
| 375 | for (steps) |s| { | |
| 376 | if (s.invalidateResult(gpa)) any_dirty = true; | |
| 377 | } | |
| 378 | } | |
| 379 | } | |
| 380 | }, | |
| 381 | } | |
| 382 | } | |
| 383 | if (any_dirty) { | |
| 384 | fse.since_event = rs.FSEventStreamGetLatestEventId(stream); | |
| 385 | _ = fse.waiting_semaphore.signal(); | |
| 386 | } | |
| 387 | } | |
| 388 | fn dirStartsWith(path: []const u8, prefix: []const u8) bool { | |
| 389 | if (std.mem.eql(u8, path, prefix)) return true; | |
| 390 | if (!std.mem.startsWith(u8, path, prefix)) return false; | |
| 391 | if (path[prefix.len] != '/') return false; // `path` is `/foo/barx`, `prefix` is `/foo/bar` | |
| 392 | return true; // `path` is `/foo/bar/...`, `prefix` is `/foo/bar` | |
| 393 | } | |
| 394 | ||
| 395 | const CFAllocatorRef = ?*const opaque {}; | |
| 396 | const CFArrayRef = *const opaque {}; | |
| 397 | const CFStringRef = *const opaque {}; | |
| 398 | const CFTimeInterval = f64; | |
| 399 | const CFIndex = i32; | |
| 400 | const CFOptionFlags = enum(u32) { _ }; | |
| 401 | const CFAllocatorRetainCallBack = *const fn (info: ?*const anyopaque) callconv(.c) *const anyopaque; | |
| 402 | const CFAllocatorReleaseCallBack = *const fn (info: ?*const anyopaque) callconv(.c) void; | |
| 403 | const CFAllocatorCopyDescriptionCallBack = *const fn (info: ?*const anyopaque) callconv(.c) CFStringRef; | |
| 404 | const CFAllocatorAllocateCallBack = *const fn (alloc_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque; | |
| 405 | const CFAllocatorReallocateCallBack = *const fn (ptr: ?*anyopaque, new_size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) ?*const anyopaque; | |
| 406 | const CFAllocatorDeallocateCallBack = *const fn (ptr: *anyopaque, info: ?*const anyopaque) callconv(.c) void; | |
| 407 | const CFAllocatorPreferredSizeCallBack = *const fn (size: CFIndex, hint: CFOptionFlags, info: ?*const anyopaque) callconv(.c) CFIndex; | |
| 408 | const CFAllocatorContext = extern struct { | |
| 409 | version: CFIndex, | |
| 410 | info: ?*anyopaque, | |
| 411 | retain: ?CFAllocatorRetainCallBack, | |
| 412 | release: ?CFAllocatorReleaseCallBack, | |
| 413 | copy_description: ?CFAllocatorCopyDescriptionCallBack, | |
| 414 | allocate: CFAllocatorAllocateCallBack, | |
| 415 | reallocate: ?CFAllocatorReallocateCallBack, | |
| 416 | deallocate: ?CFAllocatorDeallocateCallBack, | |
| 417 | preferred_size: ?CFAllocatorPreferredSizeCallBack, | |
| 418 | }; | |
| 419 | const CFArrayCallBacks = opaque {}; | |
| 420 | const CFStringEncoding = enum(u32) { | |
| 421 | invalid_id = std.math.maxInt(u32), | |
| 422 | mac_roman = 0, | |
| 423 | windows_latin_1 = 0x500, | |
| 424 | iso_latin_1 = 0x201, | |
| 425 | next_step_latin = 0xB01, | |
| 426 | ascii = 0x600, | |
| 427 | unicode = 0x100, | |
| 428 | utf8 = 0x8000100, | |
| 429 | non_lossy_ascii = 0xBFF, | |
| 430 | }; | |
| 431 | ||
| 432 | const FSEventStreamRef = *opaque {}; | |
| 433 | const ConstFSEventStreamRef = *const @typeInfo(FSEventStreamRef).pointer.child; | |
| 434 | const FSEventStreamCallback = *const fn ( | |
| 435 | stream: ConstFSEventStreamRef, | |
| 436 | client_callback_info: ?*anyopaque, | |
| 437 | num_events: usize, | |
| 438 | event_paths: *anyopaque, | |
| 439 | event_flags: [*]const FSEventStreamEventFlags, | |
| 440 | event_ids: [*]const FSEventStreamEventId, | |
| 441 | ) callconv(.c) void; | |
| 442 | const FSEventStreamContext = extern struct { | |
| 443 | version: CFIndex, | |
| 444 | info: ?*anyopaque, | |
| 445 | retain: ?CFAllocatorRetainCallBack, | |
| 446 | release: ?CFAllocatorReleaseCallBack, | |
| 447 | copy_description: ?CFAllocatorCopyDescriptionCallBack, | |
| 448 | }; | |
| 449 | const FSEventStreamEventId = enum(u64) { | |
| 450 | since_now = std.math.maxInt(u64), | |
| 451 | _, | |
| 452 | }; | |
| 453 | const FSEventStreamCreateFlags = packed struct(u32) { | |
| 454 | use_cf_types: bool = false, | |
| 455 | no_defer: bool = false, | |
| 456 | watch_root: bool = false, | |
| 457 | ignore_self: bool = false, | |
| 458 | file_events: bool = false, | |
| 459 | _: u27 = 0, | |
| 460 | }; | |
| 461 | const FSEventStreamEventFlags = packed struct(u32) { | |
| 462 | must_scan_sub_dirs: bool, | |
| 463 | user_dropped: bool, | |
| 464 | kernel_dropped: bool, | |
| 465 | event_ids_wrapped: bool, | |
| 466 | history_done: bool, | |
| 467 | root_changed: bool, | |
| 468 | mount: bool, | |
| 469 | unmount: bool, | |
| 470 | _: u24 = 0, | |
| 471 | }; | |
| 472 | ||
| 473 | const dispatch = std.c.dispatch; | |
| 474 | const std = @import("std"); | |
| 475 | const Io = std.Io; | |
| 476 | const assert = std.debug.assert; | |
| 477 | const Allocator = std.mem.Allocator; | |
| 478 | const watch_log = std.log.scoped(.watch); | |
| 479 | const FsEvents = @This(); |
lib/std/Build/WebServer.zig deleted-926| ... | ... | @@ -1,926 +0,0 @@ |
| 1 | gpa: Allocator, | |
| 2 | graph: *const Build.Graph, | |
| 3 | all_steps: []const *Build.Step, | |
| 4 | listen_address: net.IpAddress, | |
| 5 | root_prog_node: std.Progress.Node, | |
| 6 | watch: bool, | |
| 7 | ||
| 8 | tcp_server: ?net.Server, | |
| 9 | serve_task: ?Io.Future(Io.Cancelable!void), | |
| 10 | ||
| 11 | /// Uses `Io.Clock.awake`. | |
| 12 | base_timestamp: Io.Timestamp, | |
| 13 | /// The "step name" data which trails `abi.Hello`, for the steps in `all_steps`. | |
| 14 | step_names_trailing: []u8, | |
| 15 | ||
| 16 | /// The bit-packed "step status" data. Values are `abi.StepUpdate.Status`. LSBs are earlier steps. | |
| 17 | /// Accessed atomically. | |
| 18 | step_status_bits: []u8, | |
| 19 | ||
| 20 | fuzz: ?Fuzz, | |
| 21 | time_report_mutex: Io.Mutex, | |
| 22 | time_report_msgs: [][]u8, | |
| 23 | time_report_update_times: []i64, | |
| 24 | ||
| 25 | build_status: std.atomic.Value(abi.BuildStatus), | |
| 26 | /// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate` | |
| 27 | /// to increment this value. Each client thread waits for this increment with `Io.futexWaitTimeout`, so | |
| 28 | /// `notifyUpdate` will wake those threads. Updates are sent on a short interval regardless, so it | |
| 29 | /// is recommended to only use `notifyUpdate` for changes which the user should see immediately. For | |
| 30 | /// instance, we do not call `notifyUpdate` when the number of "unique runs" in the fuzzer changes, | |
| 31 | /// because this value changes quickly so this would result in constantly spamming all clients with | |
| 32 | /// an unreasonable number of packets. | |
| 33 | update_id: std.atomic.Value(u32), | |
| 34 | ||
| 35 | runner_request_mutex: Io.Mutex, | |
| 36 | runner_request_ready_cond: Io.Condition, | |
| 37 | runner_request_empty_cond: Io.Condition, | |
| 38 | runner_request: ?RunnerRequest, | |
| 39 | ||
| 40 | /// If a client is not explicitly notified of changes with `notifyUpdate`, it will be sent updates | |
| 41 | /// on a fixed interval of this many milliseconds. | |
| 42 | const default_update_interval_ms = 500; | |
| 43 | ||
| 44 | pub const base_clock: Io.Clock = .awake; | |
| 45 | ||
| 46 | /// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`. | |
| 47 | pub fn notifyUpdate(ws: *WebServer) void { | |
| 48 | _ = ws.update_id.rmw(.Add, 1, .release); | |
| 49 | ws.graph.io.futexWake(u32, &ws.update_id.raw, 16); | |
| 50 | } | |
| 51 | ||
| 52 | pub const Options = struct { | |
| 53 | gpa: Allocator, | |
| 54 | graph: *const std.Build.Graph, | |
| 55 | all_steps: []const *Build.Step, | |
| 56 | root_prog_node: std.Progress.Node, | |
| 57 | watch: bool, | |
| 58 | listen_address: net.IpAddress, | |
| 59 | base_timestamp: Io.Clock.Timestamp, | |
| 60 | }; | |
| 61 | pub fn init(opts: Options) WebServer { | |
| 62 | // The upcoming `Io` interface should allow us to use `Io.async` and `Io.concurrent` | |
| 63 | // instead of threads, so that the web server can function in single-threaded builds. | |
| 64 | comptime assert(!builtin.single_threaded); | |
| 65 | assert(opts.base_timestamp.clock == base_clock); | |
| 66 | ||
| 67 | const all_steps = opts.all_steps; | |
| 68 | ||
| 69 | const step_names_trailing = opts.gpa.alloc(u8, len: { | |
| 70 | var name_bytes: usize = 0; | |
| 71 | for (all_steps) |step| name_bytes += step.name.len; | |
| 72 | break :len name_bytes + all_steps.len * 4; | |
| 73 | }) catch @panic("out of memory"); | |
| 74 | { | |
| 75 | const step_name_lens: []align(1) u32 = @ptrCast(step_names_trailing[0 .. all_steps.len * 4]); | |
| 76 | var idx: usize = all_steps.len * 4; | |
| 77 | for (all_steps, step_name_lens) |step, *name_len| { | |
| 78 | name_len.* = @intCast(step.name.len); | |
| 79 | @memcpy(step_names_trailing[idx..][0..step.name.len], step.name); | |
| 80 | idx += step.name.len; | |
| 81 | } | |
| 82 | assert(idx == step_names_trailing.len); | |
| 83 | } | |
| 84 | ||
| 85 | const step_status_bits = opts.gpa.alloc( | |
| 86 | u8, | |
| 87 | std.math.divCeil(usize, all_steps.len, 4) catch unreachable, | |
| 88 | ) catch @panic("out of memory"); | |
| 89 | @memset(step_status_bits, 0); | |
| 90 | ||
| 91 | const time_reports_len: usize = if (opts.graph.time_report) all_steps.len else 0; | |
| 92 | const time_report_msgs = opts.gpa.alloc([]u8, time_reports_len) catch @panic("out of memory"); | |
| 93 | const time_report_update_times = opts.gpa.alloc(i64, time_reports_len) catch @panic("out of memory"); | |
| 94 | @memset(time_report_msgs, &.{}); | |
| 95 | @memset(time_report_update_times, std.math.minInt(i64)); | |
| 96 | ||
| 97 | return .{ | |
| 98 | .gpa = opts.gpa, | |
| 99 | .graph = opts.graph, | |
| 100 | .all_steps = all_steps, | |
| 101 | .listen_address = opts.listen_address, | |
| 102 | .root_prog_node = opts.root_prog_node, | |
| 103 | .watch = opts.watch, | |
| 104 | ||
| 105 | .tcp_server = null, | |
| 106 | .serve_task = null, | |
| 107 | ||
| 108 | .base_timestamp = opts.base_timestamp.raw, | |
| 109 | .step_names_trailing = step_names_trailing, | |
| 110 | ||
| 111 | .step_status_bits = step_status_bits, | |
| 112 | ||
| 113 | .fuzz = null, | |
| 114 | .time_report_mutex = .init, | |
| 115 | .time_report_msgs = time_report_msgs, | |
| 116 | .time_report_update_times = time_report_update_times, | |
| 117 | ||
| 118 | .build_status = .init(.idle), | |
| 119 | .update_id = .init(0), | |
| 120 | ||
| 121 | .runner_request_mutex = .init, | |
| 122 | .runner_request_ready_cond = .init, | |
| 123 | .runner_request_empty_cond = .init, | |
| 124 | .runner_request = null, | |
| 125 | }; | |
| 126 | } | |
| 127 | pub fn deinit(ws: *WebServer) void { | |
| 128 | const gpa = ws.gpa; | |
| 129 | const io = ws.graph.io; | |
| 130 | ||
| 131 | gpa.free(ws.step_names_trailing); | |
| 132 | gpa.free(ws.step_status_bits); | |
| 133 | ||
| 134 | if (ws.fuzz) |*f| f.deinit(); | |
| 135 | for (ws.time_report_msgs) |msg| gpa.free(msg); | |
| 136 | gpa.free(ws.time_report_msgs); | |
| 137 | gpa.free(ws.time_report_update_times); | |
| 138 | ||
| 139 | if (ws.serve_task) |t| { | |
| 140 | if (ws.tcp_server) |*s| s.stream.close(io); | |
| 141 | t.await(); | |
| 142 | } | |
| 143 | if (ws.tcp_server) |*s| s.deinit(); | |
| 144 | ||
| 145 | gpa.free(ws.step_names_trailing); | |
| 146 | } | |
| 147 | pub fn start(ws: *WebServer) error{AlreadyReported}!void { | |
| 148 | assert(ws.tcp_server == null); | |
| 149 | assert(ws.serve_task == null); | |
| 150 | const io = ws.graph.io; | |
| 151 | ||
| 152 | ws.tcp_server = ws.listen_address.listen(io, .{ .reuse_address = true }) catch |err| { | |
| 153 | log.err("failed to listen to port {d}: {t}", .{ ws.listen_address.getPort(), err }); | |
| 154 | return error.AlreadyReported; | |
| 155 | }; | |
| 156 | ws.serve_task = io.concurrent(serve, .{ws}) catch |err| { | |
| 157 | log.err("unable to spawn web server thread: {t}", .{err}); | |
| 158 | ws.tcp_server.?.deinit(io); | |
| 159 | ws.tcp_server = null; | |
| 160 | return error.AlreadyReported; | |
| 161 | }; | |
| 162 | ||
| 163 | log.info("web interface listening at http://{f}/", .{ws.tcp_server.?.socket.address}); | |
| 164 | if (ws.listen_address.getPort() == 0) { | |
| 165 | log.info("hint: pass '--webui={f}' to use the same port next time", .{ws.tcp_server.?.socket.address}); | |
| 166 | } | |
| 167 | } | |
| 168 | fn serve(ws: *WebServer) Io.Cancelable!void { | |
| 169 | const io = ws.graph.io; | |
| 170 | var group: Io.Group = .init; | |
| 171 | defer group.cancel(io); | |
| 172 | while (true) { | |
| 173 | var stream = ws.tcp_server.?.accept(io) catch |err| switch (err) { | |
| 174 | error.Canceled => |e| return e, | |
| 175 | else => |e| { | |
| 176 | log.err("failed to accept connection: {t}", .{e}); | |
| 177 | return; | |
| 178 | }, | |
| 179 | }; | |
| 180 | group.concurrent(io, accept, .{ ws, stream }) catch |err| { | |
| 181 | log.err("unable to spawn connection thread: {t}", .{err}); | |
| 182 | stream.close(io); | |
| 183 | continue; | |
| 184 | }; | |
| 185 | } | |
| 186 | } | |
| 187 | ||
| 188 | pub fn startBuild(ws: *WebServer) void { | |
| 189 | if (ws.fuzz) |*fuzz| { | |
| 190 | fuzz.deinit(); | |
| 191 | ws.fuzz = null; | |
| 192 | } | |
| 193 | for (ws.step_status_bits) |*bits| @atomicStore(u8, bits, 0, .monotonic); | |
| 194 | ws.build_status.store(.running, .monotonic); | |
| 195 | ws.notifyUpdate(); | |
| 196 | } | |
| 197 | ||
| 198 | pub fn updateStepStatus(ws: *WebServer, step: *Build.Step, new_status: abi.StepUpdate.Status) void { | |
| 199 | const step_idx: u32 = for (ws.all_steps, 0..) |s, i| { | |
| 200 | if (s == step) break @intCast(i); | |
| 201 | } else unreachable; | |
| 202 | const ptr = &ws.step_status_bits[step_idx / 4]; | |
| 203 | const bit_offset: u3 = @intCast((step_idx % 4) * 2); | |
| 204 | const old_bits: u2 = @truncate(@atomicLoad(u8, ptr, .monotonic) >> bit_offset); | |
| 205 | const mask = @as(u8, @intFromEnum(new_status) ^ old_bits) << bit_offset; | |
| 206 | _ = @atomicRmw(u8, ptr, .Xor, mask, .monotonic); | |
| 207 | ws.notifyUpdate(); | |
| 208 | } | |
| 209 | ||
| 210 | pub fn finishBuild(ws: *WebServer, opts: struct { | |
| 211 | fuzz: bool, | |
| 212 | }) void { | |
| 213 | if (opts.fuzz) { | |
| 214 | switch (builtin.os.tag) { | |
| 215 | // Current implementation depends on two things that need to be ported to Windows: | |
| 216 | // * Memory-mapping to share data between the fuzzer and build runner. | |
| 217 | // * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving | |
| 218 | // many addresses to source locations). | |
| 219 | .windows => std.process.fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}), | |
| 220 | else => {}, | |
| 221 | } | |
| 222 | if (@bitSizeOf(usize) != 64) { | |
| 223 | // Current implementation depends on posix.mmap()'s second | |
| 224 | // parameter, `length: usize`, being compatible with file system's | |
| 225 | // u64 return value. This is not the case on 32-bit platforms. | |
| 226 | // Affects or affected by issues #5185, #22523, and #22464. | |
| 227 | std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); | |
| 228 | } | |
| 229 | ||
| 230 | assert(ws.fuzz == null); | |
| 231 | ||
| 232 | ws.build_status.store(.fuzz_init, .monotonic); | |
| 233 | ws.notifyUpdate(); | |
| 234 | ||
| 235 | ws.fuzz = Fuzz.init( | |
| 236 | ws.gpa, | |
| 237 | ws.graph.io, | |
| 238 | ws.all_steps, | |
| 239 | ws.root_prog_node, | |
| 240 | .{ .forever = .{ .ws = ws } }, | |
| 241 | ) catch |err| std.process.fatal("failed to start fuzzer: {s}", .{@errorName(err)}); | |
| 242 | ws.fuzz.?.start(); | |
| 243 | } | |
| 244 | ||
| 245 | ws.build_status.store(if (ws.watch) .watching else .idle, .monotonic); | |
| 246 | ws.notifyUpdate(); | |
| 247 | } | |
| 248 | ||
| 249 | pub fn now(s: *const WebServer) i64 { | |
| 250 | const io = s.graph.io; | |
| 251 | const ts = base_clock.now(io); | |
| 252 | return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds()); | |
| 253 | } | |
| 254 | ||
| 255 | fn accept(ws: *WebServer, stream: net.Stream) void { | |
| 256 | const io = ws.graph.io; | |
| 257 | defer { | |
| 258 | // `net.Stream.close` wants to helpfully overwrite `stream` with | |
| 259 | // `undefined`, but it cannot do so since it is an immutable parameter. | |
| 260 | var copy = stream; | |
| 261 | copy.close(io); | |
| 262 | } | |
| 263 | var send_buffer: [4096]u8 = undefined; | |
| 264 | var recv_buffer: [4096]u8 = undefined; | |
| 265 | var connection_reader = stream.reader(io, &recv_buffer); | |
| 266 | var connection_writer = stream.writer(io, &send_buffer); | |
| 267 | var server: http.Server = .init(&connection_reader.interface, &connection_writer.interface); | |
| 268 | ||
| 269 | while (true) { | |
| 270 | var request = server.receiveHead() catch |err| switch (err) { | |
| 271 | error.HttpConnectionClosing => return, | |
| 272 | else => return log.err("failed to receive http request: {t}", .{err}), | |
| 273 | }; | |
| 274 | switch (request.upgradeRequested()) { | |
| 275 | .websocket => |opt_key| { | |
| 276 | const key = opt_key orelse return log.err("missing websocket key", .{}); | |
| 277 | var web_socket = request.respondWebSocket(.{ .key = key }) catch { | |
| 278 | return log.err("failed to respond web socket: {t}", .{connection_writer.err.?}); | |
| 279 | }; | |
| 280 | ws.serveWebSocket(&web_socket) catch |err| { | |
| 281 | log.err("failed to serve websocket: {t}", .{err}); | |
| 282 | return; | |
| 283 | }; | |
| 284 | comptime unreachable; | |
| 285 | }, | |
| 286 | .other => |name| return log.err("unknown upgrade request: {s}", .{name}), | |
| 287 | .none => { | |
| 288 | ws.serveRequest(&request) catch |err| switch (err) { | |
| 289 | error.AlreadyReported => return, | |
| 290 | else => { | |
| 291 | log.err("failed to serve '{s}': {t}", .{ request.head.target, err }); | |
| 292 | return; | |
| 293 | }, | |
| 294 | }; | |
| 295 | }, | |
| 296 | } | |
| 297 | } | |
| 298 | } | |
| 299 | ||
| 300 | fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { | |
| 301 | const io = ws.graph.io; | |
| 302 | ||
| 303 | var prev_build_status = ws.build_status.load(.monotonic); | |
| 304 | ||
| 305 | const prev_step_status_bits = try ws.gpa.alloc(u8, ws.step_status_bits.len); | |
| 306 | defer ws.gpa.free(prev_step_status_bits); | |
| 307 | for (prev_step_status_bits, ws.step_status_bits) |*copy, *shared| { | |
| 308 | copy.* = @atomicLoad(u8, shared, .monotonic); | |
| 309 | } | |
| 310 | ||
| 311 | var recv_thread = try io.concurrent(recvWebSocketMessages, .{ ws, sock }); | |
| 312 | defer recv_thread.cancel(io); | |
| 313 | ||
| 314 | { | |
| 315 | const hello_header: abi.Hello = .{ | |
| 316 | .status = prev_build_status, | |
| 317 | .flags = .{ | |
| 318 | .time_report = ws.graph.time_report, | |
| 319 | }, | |
| 320 | .timestamp = ws.now(), | |
| 321 | .steps_len = @intCast(ws.all_steps.len), | |
| 322 | }; | |
| 323 | var bufs: [3][]const u8 = .{ @ptrCast(&hello_header), ws.step_names_trailing, prev_step_status_bits }; | |
| 324 | try sock.writeMessageVec(&bufs, .binary); | |
| 325 | } | |
| 326 | ||
| 327 | var prev_fuzz: Fuzz.Previous = .init; | |
| 328 | var prev_time: i64 = std.math.minInt(i64); | |
| 329 | while (true) { | |
| 330 | const start_time = ws.now(); | |
| 331 | const start_update_id = ws.update_id.load(.acquire); | |
| 332 | ||
| 333 | if (ws.fuzz) |*fuzz| { | |
| 334 | try fuzz.sendUpdate(sock, &prev_fuzz); | |
| 335 | } | |
| 336 | ||
| 337 | { | |
| 338 | try ws.time_report_mutex.lock(io); | |
| 339 | defer ws.time_report_mutex.unlock(io); | |
| 340 | for (ws.time_report_msgs, ws.time_report_update_times) |msg, update_time| { | |
| 341 | if (update_time <= prev_time) continue; | |
| 342 | // We want to send `msg`, but shouldn't block `ws.time_report_mutex` while we do, so | |
| 343 | // that we don't hold up the build system on the client accepting this packet. | |
| 344 | const owned_msg = try ws.gpa.dupe(u8, msg); | |
| 345 | defer ws.gpa.free(owned_msg); | |
| 346 | // Temporarily unlock, then re-lock after the message is sent. | |
| 347 | ws.time_report_mutex.unlock(io); | |
| 348 | defer ws.time_report_mutex.lockUncancelable(io); | |
| 349 | try sock.writeMessage(owned_msg, .binary); | |
| 350 | } | |
| 351 | } | |
| 352 | ||
| 353 | { | |
| 354 | const build_status = ws.build_status.load(.monotonic); | |
| 355 | if (build_status != prev_build_status) { | |
| 356 | prev_build_status = build_status; | |
| 357 | const msg: abi.StatusUpdate = .{ .new = build_status }; | |
| 358 | try sock.writeMessage(@ptrCast(&msg), .binary); | |
| 359 | } | |
| 360 | } | |
| 361 | ||
| 362 | for (prev_step_status_bits, ws.step_status_bits, 0..) |*prev_byte, *shared, byte_idx| { | |
| 363 | const cur_byte = @atomicLoad(u8, shared, .monotonic); | |
| 364 | if (prev_byte.* == cur_byte) continue; | |
| 365 | const cur: [4]abi.StepUpdate.Status = .{ | |
| 366 | @enumFromInt(@as(u2, @truncate(cur_byte >> 0))), | |
| 367 | @enumFromInt(@as(u2, @truncate(cur_byte >> 2))), | |
| 368 | @enumFromInt(@as(u2, @truncate(cur_byte >> 4))), | |
| 369 | @enumFromInt(@as(u2, @truncate(cur_byte >> 6))), | |
| 370 | }; | |
| 371 | const prev: [4]abi.StepUpdate.Status = .{ | |
| 372 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 0))), | |
| 373 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 2))), | |
| 374 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 4))), | |
| 375 | @enumFromInt(@as(u2, @truncate(prev_byte.* >> 6))), | |
| 376 | }; | |
| 377 | for (cur, prev, byte_idx * 4..) |cur_status, prev_status, step_idx| { | |
| 378 | const msg: abi.StepUpdate = .{ .step_idx = @intCast(step_idx), .bits = .{ .status = cur_status } }; | |
| 379 | if (cur_status != prev_status) try sock.writeMessage(@ptrCast(&msg), .binary); | |
| 380 | } | |
| 381 | prev_byte.* = cur_byte; | |
| 382 | } | |
| 383 | ||
| 384 | prev_time = start_time; | |
| 385 | ||
| 386 | const old_cp = io.swapCancelProtection(.blocked); | |
| 387 | defer _ = io.swapCancelProtection(old_cp); | |
| 388 | io.futexWaitTimeout( | |
| 389 | u32, | |
| 390 | &ws.update_id.raw, | |
| 391 | start_update_id, | |
| 392 | .{ .duration = .{ | |
| 393 | .clock = .awake, | |
| 394 | .raw = .fromMilliseconds(default_update_interval_ms), | |
| 395 | } }, | |
| 396 | ) catch |err| switch (err) { | |
| 397 | error.Canceled => unreachable, | |
| 398 | }; | |
| 399 | } | |
| 400 | } | |
| 401 | fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void { | |
| 402 | const io = ws.graph.io; | |
| 403 | ||
| 404 | while (true) { | |
| 405 | const msg = sock.readSmallMessage() catch return; | |
| 406 | if (msg.opcode != .binary) continue; | |
| 407 | if (msg.data.len == 0) continue; | |
| 408 | const tag: abi.ToServerTag = @enumFromInt(msg.data[0]); | |
| 409 | switch (tag) { | |
| 410 | _ => continue, | |
| 411 | .rebuild => while (true) { | |
| 412 | ws.runner_request_mutex.lock(io) catch |err| switch (err) { | |
| 413 | error.Canceled => return, | |
| 414 | }; | |
| 415 | defer ws.runner_request_mutex.unlock(io); | |
| 416 | if (ws.runner_request == null) { | |
| 417 | ws.runner_request = .rebuild; | |
| 418 | ws.runner_request_ready_cond.signal(io); | |
| 419 | break; | |
| 420 | } | |
| 421 | ws.runner_request_empty_cond.wait(io, &ws.runner_request_mutex) catch return; | |
| 422 | }, | |
| 423 | } | |
| 424 | } | |
| 425 | } | |
| 426 | ||
| 427 | fn serveRequest(ws: *WebServer, req: *http.Server.Request) !void { | |
| 428 | // Strip an optional leading '/debug' component from the request. | |
| 429 | const target: []const u8, const debug: bool = target: { | |
| 430 | if (mem.eql(u8, req.head.target, "/debug")) break :target .{ "/", true }; | |
| 431 | if (mem.eql(u8, req.head.target, "/debug/")) break :target .{ "/", true }; | |
| 432 | if (mem.startsWith(u8, req.head.target, "/debug/")) break :target .{ req.head.target["/debug".len..], true }; | |
| 433 | break :target .{ req.head.target, false }; | |
| 434 | }; | |
| 435 | ||
| 436 | if (mem.eql(u8, target, "/")) return serveLibFile(ws, req, "build-web/index.html", "text/html"); | |
| 437 | if (mem.eql(u8, target, "/main.js")) return serveLibFile(ws, req, "build-web/main.js", "application/javascript"); | |
| 438 | if (mem.eql(u8, target, "/style.css")) return serveLibFile(ws, req, "build-web/style.css", "text/css"); | |
| 439 | if (mem.eql(u8, target, "/time_report.css")) return serveLibFile(ws, req, "build-web/time_report.css", "text/css"); | |
| 440 | if (mem.eql(u8, target, "/main.wasm")) return serveClientWasm(ws, req, if (debug) .Debug else .ReleaseFast); | |
| 441 | ||
| 442 | if (ws.fuzz) |*fuzz| { | |
| 443 | if (mem.eql(u8, target, "/sources.tar")) return fuzz.serveSourcesTar(req); | |
| 444 | } | |
| 445 | ||
| 446 | try req.respond("not found", .{ | |
| 447 | .status = .not_found, | |
| 448 | .extra_headers = &.{ | |
| 449 | .{ .name = "Content-Type", .value = "text/plain" }, | |
| 450 | }, | |
| 451 | }); | |
| 452 | } | |
| 453 | ||
| 454 | fn serveLibFile( | |
| 455 | ws: *WebServer, | |
| 456 | request: *http.Server.Request, | |
| 457 | sub_path: []const u8, | |
| 458 | content_type: []const u8, | |
| 459 | ) !void { | |
| 460 | return serveFile(ws, request, .{ | |
| 461 | .root_dir = ws.graph.zig_lib_directory, | |
| 462 | .sub_path = sub_path, | |
| 463 | }, content_type); | |
| 464 | } | |
| 465 | fn serveClientWasm( | |
| 466 | ws: *WebServer, | |
| 467 | req: *http.Server.Request, | |
| 468 | optimize_mode: std.builtin.OptimizeMode, | |
| 469 | ) !void { | |
| 470 | var arena_state: std.heap.ArenaAllocator = .init(ws.gpa); | |
| 471 | defer arena_state.deinit(); | |
| 472 | const arena = arena_state.allocator(); | |
| 473 | ||
| 474 | // We always rebuild the wasm on-the-fly, so that if it is edited the user can just refresh the page. | |
| 475 | const bin_path = try buildClientWasm(ws, arena, optimize_mode); | |
| 476 | return serveFile(ws, req, bin_path, "application/wasm"); | |
| 477 | } | |
| 478 | ||
| 479 | pub fn serveFile( | |
| 480 | ws: *WebServer, | |
| 481 | request: *http.Server.Request, | |
| 482 | path: Cache.Path, | |
| 483 | content_type: []const u8, | |
| 484 | ) !void { | |
| 485 | const gpa = ws.gpa; | |
| 486 | const io = ws.graph.io; | |
| 487 | // The desired API is actually sendfile, which will require enhancing http.Server. | |
| 488 | // We load the file with every request so that the user can make changes to the file | |
| 489 | // and refresh the HTML page without restarting this server. | |
| 490 | const file_contents = path.root_dir.handle.readFileAlloc(io, path.sub_path, gpa, .limited(10 * 1024 * 1024)) catch |err| { | |
| 491 | log.err("failed to read '{f}': {t}", .{ path, err }); | |
| 492 | return error.AlreadyReported; | |
| 493 | }; | |
| 494 | defer gpa.free(file_contents); | |
| 495 | try request.respond(file_contents, .{ | |
| 496 | .extra_headers = &.{ | |
| 497 | .{ .name = "Content-Type", .value = content_type }, | |
| 498 | cache_control_header, | |
| 499 | }, | |
| 500 | }); | |
| 501 | } | |
| 502 | pub fn serveTarFile(ws: *WebServer, request: *http.Server.Request, paths: []const Cache.Path) !void { | |
| 503 | const graph = ws.graph; | |
| 504 | const io = graph.io; | |
| 505 | ||
| 506 | var send_buffer: [0x4000]u8 = undefined; | |
| 507 | var response = try request.respondStreaming(&send_buffer, .{ | |
| 508 | .respond_options = .{ | |
| 509 | .extra_headers = &.{ | |
| 510 | .{ .name = "Content-Type", .value = "application/x-tar" }, | |
| 511 | cache_control_header, | |
| 512 | }, | |
| 513 | }, | |
| 514 | }); | |
| 515 | ||
| 516 | var archiver: std.tar.Writer = .{ .underlying_writer = &response.writer }; | |
| 517 | ||
| 518 | for (paths) |path| { | |
| 519 | var file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| { | |
| 520 | log.err("failed to open '{f}': {s}", .{ path, @errorName(err) }); | |
| 521 | continue; | |
| 522 | }; | |
| 523 | defer file.close(io); | |
| 524 | const stat = try file.stat(io); | |
| 525 | var read_buffer: [1024]u8 = undefined; | |
| 526 | var file_reader: Io.File.Reader = .initSize(file, io, &read_buffer, stat.size); | |
| 527 | ||
| 528 | // TODO: this logic is completely bogus -- obviously so, because `path.root_dir.path` can | |
| 529 | // be cwd-relative. This is also related to why linkification doesn't work in the fuzzer UI: | |
| 530 | // it turns out the WASM treats the first path component as the module name, typically | |
| 531 | // resulting in modules named "" and "src". The compiler needs to tell the build system | |
| 532 | // about the module graph so that the build system can correctly encode this information in | |
| 533 | // the tar file. | |
| 534 | // | |
| 535 | // Additionally, this needs to ensure that all path separators for both prefix and | |
| 536 | // sub_path are using the POSIX-style `/` on platforms that don't use it as their native | |
| 537 | // path separator. | |
| 538 | archiver.prefix = path.root_dir.path orelse graph.cache.cwd; | |
| 539 | try archiver.writeFile(path.sub_path, &file_reader, @intCast(stat.mtime.toSeconds())); | |
| 540 | } | |
| 541 | ||
| 542 | // intentionally not calling `archiver.finishPedantically` | |
| 543 | try response.end(); | |
| 544 | } | |
| 545 | ||
| 546 | fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.OptimizeMode) !Cache.Path { | |
| 547 | const root_name = "build-web"; | |
| 548 | const arch_os_abi = "wasm32-freestanding"; | |
| 549 | const cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; | |
| 550 | ||
| 551 | const gpa = ws.gpa; | |
| 552 | const graph = ws.graph; | |
| 553 | const io = graph.io; | |
| 554 | ||
| 555 | const main_src_path: Cache.Path = .{ | |
| 556 | .root_dir = graph.zig_lib_directory, | |
| 557 | .sub_path = "build-web/main.zig", | |
| 558 | }; | |
| 559 | const walk_src_path: Cache.Path = .{ | |
| 560 | .root_dir = graph.zig_lib_directory, | |
| 561 | .sub_path = "docs/wasm/Walk.zig", | |
| 562 | }; | |
| 563 | const html_render_src_path: Cache.Path = .{ | |
| 564 | .root_dir = graph.zig_lib_directory, | |
| 565 | .sub_path = "docs/wasm/html_render.zig", | |
| 566 | }; | |
| 567 | ||
| 568 | var argv: std.ArrayList([]const u8) = .empty; | |
| 569 | ||
| 570 | try argv.appendSlice(arena, &.{ | |
| 571 | graph.zig_exe, "build-exe", // | |
| 572 | "-fno-entry", // | |
| 573 | "-O", @tagName(optimize), // | |
| 574 | "-target", arch_os_abi, // | |
| 575 | "-mcpu", cpu_features, // | |
| 576 | "--cache-dir", graph.global_cache_root.path orelse ".", // | |
| 577 | "--global-cache-dir", graph.global_cache_root.path orelse ".", // | |
| 578 | "--zig-lib-dir", graph.zig_lib_directory.path orelse ".", // | |
| 579 | "--name", root_name, // | |
| 580 | "-rdynamic", // | |
| 581 | "-fsingle-threaded", // | |
| 582 | "--dep", "Walk", // | |
| 583 | "--dep", "html_render", // | |
| 584 | try std.fmt.allocPrint(arena, "-Mroot={f}", .{main_src_path}), // | |
| 585 | try std.fmt.allocPrint(arena, "-MWalk={f}", .{walk_src_path}), // | |
| 586 | "--dep", "Walk", // | |
| 587 | try std.fmt.allocPrint(arena, "-Mhtml_render={f}", .{html_render_src_path}), // | |
| 588 | "--listen=-", | |
| 589 | }); | |
| 590 | ||
| 591 | var child = try std.process.spawn(io, .{ | |
| 592 | .argv = argv.items, | |
| 593 | .environ_map = &graph.environ_map, | |
| 594 | .stdin = .pipe, | |
| 595 | .stdout = .pipe, | |
| 596 | .stderr = .pipe, | |
| 597 | }); | |
| 598 | defer child.kill(io); | |
| 599 | ||
| 600 | var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited }); | |
| 601 | defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {}; | |
| 602 | ||
| 603 | var stdout_buffer: [512]u8 = undefined; | |
| 604 | var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer); | |
| 605 | const stdout = &stdout_reader.interface; | |
| 606 | ||
| 607 | { | |
| 608 | var w = child.stdin.?.writer(io, &.{}); | |
| 609 | w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .update, .bytes_len = 0 }, .little) catch |err| switch (err) { | |
| 610 | error.WriteFailed => return w.err.?, | |
| 611 | }; | |
| 612 | w.interface.writeStruct(std.zig.Client.Message.Header{ .tag = .exit, .bytes_len = 0 }, .little) catch |err| switch (err) { | |
| 613 | error.WriteFailed => return w.err.?, | |
| 614 | }; | |
| 615 | } | |
| 616 | ||
| 617 | const Header = std.zig.Server.Message.Header; | |
| 618 | ||
| 619 | var result: ?Cache.Path = null; | |
| 620 | var result_error_bundle = std.zig.ErrorBundle.empty; | |
| 621 | var body_buffer: std.ArrayList(u8) = .empty; | |
| 622 | defer body_buffer.deinit(gpa); | |
| 623 | ||
| 624 | while (true) { | |
| 625 | const header = stdout.takeStruct(Header, .little) catch |err| switch (err) { | |
| 626 | error.ReadFailed => |e| return e, | |
| 627 | error.EndOfStream => break, | |
| 628 | }; | |
| 629 | body_buffer.clearRetainingCapacity(); | |
| 630 | try stdout.appendExact(gpa, &body_buffer, header.bytes_len); | |
| 631 | const body = body_buffer.items; | |
| 632 | ||
| 633 | switch (header.tag) { | |
| 634 | .zig_version => { | |
| 635 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { | |
| 636 | return error.ZigProtocolVersionMismatch; | |
| 637 | } | |
| 638 | }, | |
| 639 | .error_bundle => { | |
| 640 | result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); | |
| 641 | }, | |
| 642 | .emit_digest => { | |
| 643 | const EmitDigest = std.zig.Server.Message.EmitDigest; | |
| 644 | const ebp_hdr: *align(1) const EmitDigest = @ptrCast(body); | |
| 645 | if (!ebp_hdr.flags.cache_hit) { | |
| 646 | log.info("source changes detected; rebuilt wasm component", .{}); | |
| 647 | } | |
| 648 | const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len]; | |
| 649 | result = .{ | |
| 650 | .root_dir = graph.global_cache_root, | |
| 651 | .sub_path = try arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*)), | |
| 652 | }; | |
| 653 | }, | |
| 654 | else => {}, // ignore other messages | |
| 655 | } | |
| 656 | } | |
| 657 | ||
| 658 | const stderr_contents = try stderr_task.await(io); | |
| 659 | if (stderr_contents.len > 0) { | |
| 660 | std.debug.print("{s}", .{stderr_contents}); | |
| 661 | } | |
| 662 | ||
| 663 | // Send EOF to stdin. | |
| 664 | child.stdin.?.close(io); | |
| 665 | child.stdin = null; | |
| 666 | ||
| 667 | switch (try child.wait(io)) { | |
| 668 | .exited => |code| { | |
| 669 | if (code != 0) { | |
| 670 | log.err( | |
| 671 | "the following command exited with error code {d}:\n{s}", | |
| 672 | .{ code, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, | |
| 673 | ); | |
| 674 | return error.WasmCompilationFailed; | |
| 675 | } | |
| 676 | }, | |
| 677 | .signal => |sig| { | |
| 678 | log.err( | |
| 679 | "the following command terminated with signal {t}:\n{s}", | |
| 680 | .{ sig, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, | |
| 681 | ); | |
| 682 | return error.WasmCompilationFailed; | |
| 683 | }, | |
| 684 | .stopped => |sig| { | |
| 685 | log.err( | |
| 686 | "the following command stopped unexpectedly with signal {t}:\n{s}", | |
| 687 | .{ sig, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, | |
| 688 | ); | |
| 689 | return error.WasmCompilationFailed; | |
| 690 | }, | |
| 691 | .unknown => { | |
| 692 | log.err( | |
| 693 | "the following command terminated unexpectedly:\n{s}", | |
| 694 | .{try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)}, | |
| 695 | ); | |
| 696 | return error.WasmCompilationFailed; | |
| 697 | }, | |
| 698 | } | |
| 699 | ||
| 700 | if (result_error_bundle.errorMessageCount() > 0) { | |
| 701 | try result_error_bundle.renderToStderr(io, .{}, .auto); | |
| 702 | log.err("the following command failed with {d} compilation errors:\n{s}", .{ | |
| 703 | result_error_bundle.errorMessageCount(), | |
| 704 | try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), | |
| 705 | }); | |
| 706 | return error.WasmCompilationFailed; | |
| 707 | } | |
| 708 | ||
| 709 | const base_path = result orelse { | |
| 710 | log.err("child process failed to report result\n{s}", .{ | |
| 711 | try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), | |
| 712 | }); | |
| 713 | return error.WasmCompilationFailed; | |
| 714 | }; | |
| 715 | const bin_name = try std.zig.binNameAlloc(arena, .{ | |
| 716 | .root_name = root_name, | |
| 717 | .target = &(std.zig.system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{ | |
| 718 | .arch_os_abi = arch_os_abi, | |
| 719 | .cpu_features = cpu_features, | |
| 720 | }) catch unreachable) catch unreachable), | |
| 721 | .output_mode = .Exe, | |
| 722 | }); | |
| 723 | return base_path.join(arena, bin_name); | |
| 724 | } | |
| 725 | ||
| 726 | fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 { | |
| 727 | var file_reader: Io.File.Reader = .initStreaming(file, io, &.{}); | |
| 728 | return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) { | |
| 729 | error.ReadFailed => return file_reader.err.?, | |
| 730 | else => |e| return e, | |
| 731 | }; | |
| 732 | } | |
| 733 | ||
| 734 | pub fn updateTimeReportCompile(ws: *WebServer, opts: struct { | |
| 735 | compile: *Build.Step.Compile, | |
| 736 | ||
| 737 | use_llvm: bool, | |
| 738 | stats: abi.time_report.CompileResult.Stats, | |
| 739 | ns_total: u64, | |
| 740 | ||
| 741 | llvm_pass_timings_len: u32, | |
| 742 | files_len: u32, | |
| 743 | decls_len: u32, | |
| 744 | ||
| 745 | /// The trailing data of `abi.time_report.CompileResult`, except the step name. | |
| 746 | trailing: []const u8, | |
| 747 | }) void { | |
| 748 | const gpa = ws.gpa; | |
| 749 | const io = ws.graph.io; | |
| 750 | ||
| 751 | const step_idx: u32 = for (ws.all_steps, 0..) |s, i| { | |
| 752 | if (s == &opts.compile.step) break @intCast(i); | |
| 753 | } else unreachable; | |
| 754 | ||
| 755 | const old_buf = old: { | |
| 756 | ws.time_report_mutex.lock(io) catch return; | |
| 757 | defer ws.time_report_mutex.unlock(io); | |
| 758 | const old = ws.time_report_msgs[step_idx]; | |
| 759 | ws.time_report_msgs[step_idx] = &.{}; | |
| 760 | break :old old; | |
| 761 | }; | |
| 762 | const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.CompileResult) + opts.trailing.len) catch @panic("out of memory"); | |
| 763 | ||
| 764 | const out_header: *align(1) abi.time_report.CompileResult = @ptrCast(buf[0..@sizeOf(abi.time_report.CompileResult)]); | |
| 765 | out_header.* = .{ | |
| 766 | .step_idx = step_idx, | |
| 767 | .flags = .{ | |
| 768 | .use_llvm = opts.use_llvm, | |
| 769 | }, | |
| 770 | .stats = opts.stats, | |
| 771 | .ns_total = opts.ns_total, | |
| 772 | .llvm_pass_timings_len = opts.llvm_pass_timings_len, | |
| 773 | .files_len = opts.files_len, | |
| 774 | .decls_len = opts.decls_len, | |
| 775 | }; | |
| 776 | @memcpy(buf[@sizeOf(abi.time_report.CompileResult)..], opts.trailing); | |
| 777 | ||
| 778 | { | |
| 779 | ws.time_report_mutex.lock(io) catch return; | |
| 780 | defer ws.time_report_mutex.unlock(io); | |
| 781 | assert(ws.time_report_msgs[step_idx].len == 0); | |
| 782 | ws.time_report_msgs[step_idx] = buf; | |
| 783 | ws.time_report_update_times[step_idx] = ws.now(); | |
| 784 | } | |
| 785 | ws.notifyUpdate(); | |
| 786 | } | |
| 787 | ||
| 788 | pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, duration: Io.Duration) void { | |
| 789 | const gpa = ws.gpa; | |
| 790 | const io = ws.graph.io; | |
| 791 | ||
| 792 | const step_idx: u32 = for (ws.all_steps, 0..) |s, i| { | |
| 793 | if (s == step) break @intCast(i); | |
| 794 | } else unreachable; | |
| 795 | ||
| 796 | const old_buf = old: { | |
| 797 | ws.time_report_mutex.lock(io) catch return; | |
| 798 | defer ws.time_report_mutex.unlock(io); | |
| 799 | const old = ws.time_report_msgs[step_idx]; | |
| 800 | ws.time_report_msgs[step_idx] = &.{}; | |
| 801 | break :old old; | |
| 802 | }; | |
| 803 | const buf = gpa.realloc(old_buf, @sizeOf(abi.time_report.GenericResult)) catch @panic("out of memory"); | |
| 804 | const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf); | |
| 805 | out.* = .{ | |
| 806 | .step_idx = step_idx, | |
| 807 | .ns_total = @intCast(duration.toNanoseconds()), | |
| 808 | }; | |
| 809 | { | |
| 810 | ws.time_report_mutex.lock(io) catch return; | |
| 811 | defer ws.time_report_mutex.unlock(io); | |
| 812 | assert(ws.time_report_msgs[step_idx].len == 0); | |
| 813 | ws.time_report_msgs[step_idx] = buf; | |
| 814 | ws.time_report_update_times[step_idx] = ws.now(); | |
| 815 | } | |
| 816 | ws.notifyUpdate(); | |
| 817 | } | |
| 818 | ||
| 819 | pub fn updateTimeReportRunTest( | |
| 820 | ws: *WebServer, | |
| 821 | run: *Build.Step.Run, | |
| 822 | tests: *const Build.Step.Run.CachedTestMetadata, | |
| 823 | ns_per_test: []const u64, | |
| 824 | ) void { | |
| 825 | const gpa = ws.gpa; | |
| 826 | const io = ws.graph.io; | |
| 827 | ||
| 828 | const step_idx: u32 = for (ws.all_steps, 0..) |s, i| { | |
| 829 | if (s == &run.step) break @intCast(i); | |
| 830 | } else unreachable; | |
| 831 | ||
| 832 | assert(tests.names.len == ns_per_test.len); | |
| 833 | const tests_len: u32 = @intCast(tests.names.len); | |
| 834 | ||
| 835 | const new_len: u64 = len: { | |
| 836 | var names_len: u64 = 0; | |
| 837 | for (0..tests_len) |i| { | |
| 838 | names_len += tests.testName(@intCast(i)).len + 1; | |
| 839 | } | |
| 840 | break :len @sizeOf(abi.time_report.RunTestResult) + names_len + 8 * tests_len; | |
| 841 | }; | |
| 842 | const old_buf = old: { | |
| 843 | ws.time_report_mutex.lock(io) catch return; | |
| 844 | defer ws.time_report_mutex.unlock(io); | |
| 845 | const old = ws.time_report_msgs[step_idx]; | |
| 846 | ws.time_report_msgs[step_idx] = &.{}; | |
| 847 | break :old old; | |
| 848 | }; | |
| 849 | const buf = gpa.realloc(old_buf, new_len) catch @panic("out of memory"); | |
| 850 | ||
| 851 | const out_header: *align(1) abi.time_report.RunTestResult = @ptrCast(buf[0..@sizeOf(abi.time_report.RunTestResult)]); | |
| 852 | out_header.* = .{ | |
| 853 | .step_idx = step_idx, | |
| 854 | .tests_len = tests_len, | |
| 855 | }; | |
| 856 | var offset: usize = @sizeOf(abi.time_report.RunTestResult); | |
| 857 | const ns_per_test_out: []align(1) u64 = @ptrCast(buf[offset..][0 .. tests_len * 8]); | |
| 858 | @memcpy(ns_per_test_out, ns_per_test); | |
| 859 | offset += tests_len * 8; | |
| 860 | for (0..tests_len) |i| { | |
| 861 | const name = tests.testName(@intCast(i)); | |
| 862 | @memcpy(buf[offset..][0..name.len], name); | |
| 863 | buf[offset..][name.len] = 0; | |
| 864 | offset += name.len + 1; | |
| 865 | } | |
| 866 | assert(offset == buf.len); | |
| 867 | ||
| 868 | { | |
| 869 | ws.time_report_mutex.lock(io) catch return; | |
| 870 | defer ws.time_report_mutex.unlock(io); | |
| 871 | assert(ws.time_report_msgs[step_idx].len == 0); | |
| 872 | ws.time_report_msgs[step_idx] = buf; | |
| 873 | ws.time_report_update_times[step_idx] = ws.now(); | |
| 874 | } | |
| 875 | ws.notifyUpdate(); | |
| 876 | } | |
| 877 | ||
| 878 | const RunnerRequest = union(enum) { | |
| 879 | rebuild, | |
| 880 | }; | |
| 881 | pub fn getRunnerRequest(ws: *WebServer) ?RunnerRequest { | |
| 882 | const io = ws.graph.io; | |
| 883 | ws.runner_request_mutex.lock(io) catch return; | |
| 884 | defer ws.runner_request_mutex.unlock(io); | |
| 885 | if (ws.runner_request) |req| { | |
| 886 | ws.runner_request = null; | |
| 887 | ws.runner_request_empty_cond.signal(); | |
| 888 | return req; | |
| 889 | } | |
| 890 | return null; | |
| 891 | } | |
| 892 | pub fn wait(ws: *WebServer) Io.Cancelable!RunnerRequest { | |
| 893 | const io = ws.graph.io; | |
| 894 | try ws.runner_request_mutex.lock(io); | |
| 895 | defer ws.runner_request_mutex.unlock(io); | |
| 896 | while (true) { | |
| 897 | if (ws.runner_request) |req| { | |
| 898 | ws.runner_request = null; | |
| 899 | ws.runner_request_empty_cond.signal(io); | |
| 900 | return req; | |
| 901 | } | |
| 902 | try ws.runner_request_ready_cond.wait(io, &ws.runner_request_mutex); | |
| 903 | } | |
| 904 | } | |
| 905 | ||
| 906 | const cache_control_header: http.Header = .{ | |
| 907 | .name = "Cache-Control", | |
| 908 | .value = "max-age=0, must-revalidate", | |
| 909 | }; | |
| 910 | ||
| 911 | const builtin = @import("builtin"); | |
| 912 | ||
| 913 | const std = @import("std"); | |
| 914 | const Io = std.Io; | |
| 915 | const net = std.Io.net; | |
| 916 | const assert = std.debug.assert; | |
| 917 | const mem = std.mem; | |
| 918 | const log = std.log.scoped(.web_server); | |
| 919 | const Allocator = std.mem.Allocator; | |
| 920 | const Build = std.Build; | |
| 921 | const Cache = Build.Cache; | |
| 922 | const Fuzz = Build.Fuzz; | |
| 923 | const abi = Build.abi; | |
| 924 | const http = std.http; | |
| 925 | ||
| 926 | const WebServer = @This(); |
lib/std/Io/Dir.zig+3| ... | ... | @@ -409,7 +409,10 @@ pub const PathNameError = error{ |
| 409 | 409 | }; |
| 410 | 410 | |
| 411 | 411 | pub const AccessError = error{ |
| 412 | /// The requested `AccessOptions` would be denied to the file, or search | |
| 413 | /// permission is denied for one of the directories in the path prefix. | |
| 412 | 414 | AccessDenied, |
| 415 | /// Write permission was requested but the file is immutable. | |
| 413 | 416 | PermissionDenied, |
| 414 | 417 | FileNotFound, |
| 415 | 418 | InputOutput, |
lib/std/Io/Writer.zig+21| ... | ... | @@ -1172,6 +1172,14 @@ pub fn printValue( |
| 1172 | 1172 | }, |
| 1173 | 1173 | else => invalidFmtError(fmt, value), |
| 1174 | 1174 | }, |
| 1175 | 'q' => switch (@typeInfo(T)) { | |
| 1176 | .pointer => |info| switch (info.size) { | |
| 1177 | .one, .slice => return printStringEscaped(w, value), | |
| 1178 | .many, .c => return printStringEscaped(w, std.mem.span(value)), | |
| 1179 | }, | |
| 1180 | .array => return printStringEscaped(w, &value), | |
| 1181 | else => invalidFmtError(fmt, value), | |
| 1182 | }, | |
| 1175 | 1183 | 'B' => switch (@typeInfo(T)) { |
| 1176 | 1184 | .int, .comptime_int => return w.printByteSize(value, .decimal, options), |
| 1177 | 1185 | .@"struct" => return value.formatByteSize(w, .decimal), |
| ... | ... | @@ -1448,6 +1456,14 @@ fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void { |
| 1448 | 1456 | try w.writeByte(')'); |
| 1449 | 1457 | } |
| 1450 | 1458 | |
| 1459 | /// Prints a double quote, then escapes a string according to Zig string | |
| 1460 | /// literal rules, then a double quote. | |
| 1461 | pub fn printStringEscaped(w: *Writer, bytes: []const u8) Error!void { | |
| 1462 | try w.writeByte('"'); | |
| 1463 | try std.zig.stringEscape(bytes, w); | |
| 1464 | try w.writeByte('"'); | |
| 1465 | } | |
| 1466 | ||
| 1451 | 1467 | pub fn printVector( |
| 1452 | 1468 | w: *Writer, |
| 1453 | 1469 | comptime fmt: []const u8, |
| ... | ... | @@ -2102,6 +2118,11 @@ test "printFloat with comptime_float" { |
| 2102 | 2118 | try testing.expectFmt("1", "{}", .{1.0}); |
| 2103 | 2119 | } |
| 2104 | 2120 | |
| 2121 | test "{q} format string" { | |
| 2122 | const data: []const u8 = "i\tlike\"cheese\x00\x05cheese"; | |
| 2123 | try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data}); | |
| 2124 | } | |
| 2125 | ||
| 2105 | 2126 | fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void { |
| 2106 | 2127 | var buffer: [100]u8 = undefined; |
| 2107 | 2128 | var w: Writer = .fixed(&buffer); |
lib/std/Target.zig+2-2| ... | ... | @@ -1668,13 +1668,13 @@ pub const Cpu = struct { |
| 1668 | 1668 | }; |
| 1669 | 1669 | } |
| 1670 | 1670 | |
| 1671 | pub fn parseCpuModel(arch: Arch, cpu_name: []const u8) !*const Cpu.Model { | |
| 1671 | pub fn parseCpuModel(arch: Arch, cpu_name: []const u8) ?*const Cpu.Model { | |
| 1672 | 1672 | for (arch.allCpuModels()) |cpu| { |
| 1673 | 1673 | if (std.mem.eql(u8, cpu_name, cpu.name)) { |
| 1674 | 1674 | return cpu; |
| 1675 | 1675 | } |
| 1676 | 1676 | } |
| 1677 | return error.UnknownCpuModel; | |
| 1677 | return null; | |
| 1678 | 1678 | } |
| 1679 | 1679 | |
| 1680 | 1680 | pub fn endian(arch: Arch) std.builtin.Endian { |
lib/std/Target/Query.zig+1-1| ... | ... | @@ -282,7 +282,7 @@ pub fn parse(args: ParseOptions) !Query { |
| 282 | 282 | } else if (mem.eql(u8, cpu_name, "baseline")) { |
| 283 | 283 | result.cpu_model = .baseline; |
| 284 | 284 | } else { |
| 285 | result.cpu_model = .{ .explicit = try arch.parseCpuModel(cpu_name) }; | |
| 285 | result.cpu_model = .{ .explicit = arch.parseCpuModel(cpu_name) orelse return error.UnknownCpuModel }; | |
| 286 | 286 | } |
| 287 | 287 | |
| 288 | 288 | while (index < cpu_features.len) { |
lib/std/array_list.zig+14-8| ... | ... | @@ -1391,12 +1391,19 @@ pub fn Aligned(comptime T: type, comptime alignment: ?mem.Alignment) type { |
| 1391 | 1391 | return self.allocatedSlice()[self.items.len..]; |
| 1392 | 1392 | } |
| 1393 | 1393 | |
| 1394 | /// Returns the last element from the list, or `null` if the list is empty. | |
| 1394 | /// Deprecated in favor of `last`. | |
| 1395 | 1395 | pub fn getLast(self: Self) ?T { |
| 1396 | 1396 | if (self.items.len == 0) return null; |
| 1397 | 1397 | return self.items[self.items.len - 1]; |
| 1398 | 1398 | } |
| 1399 | 1399 | |
| 1400 | /// Returns a pointer to the last element from the list, or `null` if | |
| 1401 | /// the list is empty. | |
| 1402 | pub fn last(self: Self) ?*T { | |
| 1403 | if (self.items.len == 0) return null; | |
| 1404 | return &self.items[self.items.len - 1]; | |
| 1405 | } | |
| 1406 | ||
| 1400 | 1407 | /// Called when memory growth is necessary. Returns a capacity larger than |
| 1401 | 1408 | /// minimum that grows super-linearly. |
| 1402 | 1409 | pub fn growCapacity(minimum: usize) usize { |
| ... | ... | @@ -2378,17 +2385,16 @@ test "Managed(?u32).pop()" { |
| 2378 | 2385 | try testing.expect(list.pop() == null); |
| 2379 | 2386 | } |
| 2380 | 2387 | |
| 2381 | test "Managed(u32).getLast()" { | |
| 2388 | test "last" { | |
| 2382 | 2389 | const a = testing.allocator; |
| 2383 | 2390 | |
| 2384 | var list = Managed(u32).init(a); | |
| 2385 | defer list.deinit(); | |
| 2391 | var list: ArrayList(u32) = .empty; | |
| 2392 | defer list.deinit(a); | |
| 2386 | 2393 | |
| 2387 | try testing.expectEqual(list.getLast(), null); | |
| 2394 | try testing.expectEqual(list.last(), null); | |
| 2388 | 2395 | |
| 2389 | try list.append(2); | |
| 2390 | const const_list = list; | |
| 2391 | try testing.expectEqual(const_list.getLast().?, 2); | |
| 2396 | try list.append(a, 2); | |
| 2397 | try testing.expectEqual(list.last().?.*, 2); | |
| 2392 | 2398 | } |
| 2393 | 2399 | |
| 2394 | 2400 | test "return OutOfMemory when capacity would exceed maximum usize integer value" { |
lib/std/lang.zig+2-2| ... | ... | @@ -93,7 +93,7 @@ pub const AtomicRmwOp = enum { |
| 93 | 93 | /// |
| 94 | 94 | /// This data structure is used by the Zig language code generation and |
| 95 | 95 | /// therefore must be kept in sync with the compiler implementation. |
| 96 | pub const CodeModel = enum { | |
| 96 | pub const CodeModel = enum(u4) { | |
| 97 | 97 | default, |
| 98 | 98 | extreme, |
| 99 | 99 | kernel, |
| ... | ... | @@ -873,7 +873,7 @@ pub const OutputMode = enum { |
| 873 | 873 | |
| 874 | 874 | /// This data structure is used by the Zig language code generation and |
| 875 | 875 | /// therefore must be kept in sync with the compiler implementation. |
| 876 | pub const LinkMode = enum { | |
| 876 | pub const LinkMode = enum(u1) { | |
| 877 | 877 | static, |
| 878 | 878 | dynamic, |
| 879 | 879 | }; |
lib/std/mem/Allocator.zig+3-3| ... | ... | @@ -169,7 +169,7 @@ pub fn create(a: Allocator, comptime T: type) Error!*T { |
| 169 | 169 | const ptr = comptime std.mem.alignBackward(usize, math.maxInt(usize), @alignOf(T)); |
| 170 | 170 | return @ptrFromInt(ptr); |
| 171 | 171 | } |
| 172 | const ptr: *T = @ptrCast(try a.allocBytesWithAlignment(.of(T), @sizeOf(T), @returnAddress())); | |
| 172 | const ptr: *T = @ptrCast(try a.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress())); | |
| 173 | 173 | return ptr; |
| 174 | 174 | } |
| 175 | 175 | |
| ... | ... | @@ -285,10 +285,10 @@ fn allocWithSizeAndAlignment( |
| 285 | 285 | return_address: usize, |
| 286 | 286 | ) Error![*]align(alignment.toByteUnits()) u8 { |
| 287 | 287 | const byte_count = math.mul(usize, size, n) catch return error.OutOfMemory; |
| 288 | return self.allocBytesWithAlignment(alignment, byte_count, return_address); | |
| 288 | return self.allocBytesAligned(alignment, byte_count, return_address); | |
| 289 | 289 | } |
| 290 | 290 | |
| 291 | fn allocBytesWithAlignment( | |
| 291 | pub fn allocBytesAligned( | |
| 292 | 292 | self: Allocator, |
| 293 | 293 | comptime alignment: Alignment, |
| 294 | 294 | byte_count: usize, |
lib/std/process.zig+7| ... | ... | @@ -1113,3 +1113,10 @@ test protectMemory { |
| 1113 | 1113 | protectMemory(&test_page, .{}) catch return error.SkipZigTest; |
| 1114 | 1114 | protectMemory(&test_page, .{ .read = true, .write = true }) catch return error.SkipZigTest; |
| 1115 | 1115 | } |
| 1116 | ||
| 1117 | test { | |
| 1118 | _ = Child; | |
| 1119 | _ = Args; | |
| 1120 | _ = Environ; | |
| 1121 | _ = Preopens; | |
| 1122 | } |
lib/std/process/Child.zig+20| ... | ... | @@ -96,6 +96,22 @@ pub const Term = union(enum) { |
| 96 | 96 | signal: std.posix.SIG, |
| 97 | 97 | stopped: std.posix.SIG, |
| 98 | 98 | unknown: u32, |
| 99 | ||
| 100 | pub fn success(t: Term) bool { | |
| 101 | return switch (t) { | |
| 102 | .exited => |code| code == 0, | |
| 103 | else => false, | |
| 104 | }; | |
| 105 | } | |
| 106 | ||
| 107 | pub fn format(t: Term, w: *Io.Writer) Io.Writer.Error!void { | |
| 108 | switch (t) { | |
| 109 | .exited => |code| return w.print("exited with code {d}", .{code}), | |
| 110 | .signal => |sig| return w.print("terminated with signal {t}", .{sig}), | |
| 111 | .stopped => |sig| return w.print("stopped with signal {t}", .{sig}), | |
| 112 | .unknown => return w.writeAll("terminated unexpectedly"), | |
| 113 | } | |
| 114 | } | |
| 99 | 115 | }; |
| 100 | 116 | |
| 101 | 117 | pub const Cwd = union(enum) { |
| ... | ... | @@ -135,3 +151,7 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { |
| 135 | 151 | assert(child.id != null); |
| 136 | 152 | return io.vtable.childWait(io.userdata, child); |
| 137 | 153 | } |
| 154 | ||
| 155 | test { | |
| 156 | _ = Term; | |
| 157 | } |
lib/std/process/Environ.zig+27-3| ... | ... | @@ -96,6 +96,7 @@ pub const WindowsBlock = struct { |
| 96 | 96 | } |
| 97 | 97 | }; |
| 98 | 98 | |
| 99 | /// Each key and each value are allocated independently and owned by this data structure. | |
| 99 | 100 | pub const Map = struct { |
| 100 | 101 | array_hash_map: ArrayHashMap, |
| 101 | 102 | allocator: Allocator, |
| ... | ... | @@ -340,9 +341,6 @@ pub const Map = struct { |
| 340 | 341 | /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily |
| 341 | 342 | /// the same allocator used to allocate `em`. |
| 342 | 343 | pub fn clone(m: *const Map, gpa: Allocator) Allocator.Error!Map { |
| 343 | // Since we need to dupe the keys and values, the only way for error handling to not be a | |
| 344 | // nightmare is to add keys to an empty map one-by-one. This could be avoided if this | |
| 345 | // abstraction were a bit less... OOP-esque. | |
| 346 | 344 | var new: Map = .init(gpa); |
| 347 | 345 | errdefer new.deinit(); |
| 348 | 346 | try new.array_hash_map.ensureUnusedCapacity(gpa, m.array_hash_map.count()); |
| ... | ... | @@ -352,6 +350,32 @@ pub const Map = struct { |
| 352 | 350 | return new; |
| 353 | 351 | } |
| 354 | 352 | |
| 353 | /// Adds all the key-value pairs from `other` into this `m`. | |
| 354 | pub fn putAll(m: *Map, other: *const Map) Allocator.Error!void { | |
| 355 | const gpa = m.allocator; | |
| 356 | try m.array_hash_map.ensureUnusedCapacity(gpa, other.array_hash_map.count()); | |
| 357 | const start = m.count(); | |
| 358 | errdefer while (m.array_hash_map.count() > start) { | |
| 359 | const kv = m.array_hash_map.pop().?; | |
| 360 | gpa.free(kv.key); | |
| 361 | gpa.free(kv.value); | |
| 362 | }; | |
| 363 | for (other.array_hash_map.keys(), other.array_hash_map.values()) |key, value| { | |
| 364 | try m.put(key, value); | |
| 365 | } | |
| 366 | } | |
| 367 | ||
| 368 | /// Set the length to zero, freeing all key and value memory, not freeing | |
| 369 | /// the allocation for the entries. | |
| 370 | pub fn clearRetainingCapacity(m: *Map) void { | |
| 371 | const gpa = m.allocator; | |
| 372 | for (m.array_hash_map.keys(), m.array_hash_map.values()) |k, v| { | |
| 373 | gpa.free(k); | |
| 374 | gpa.free(v); | |
| 375 | } | |
| 376 | m.array_hash_map.clearRetainingCapacity(); | |
| 377 | } | |
| 378 | ||
| 355 | 379 | /// Creates a null-delimited environment variable block in the format |
| 356 | 380 | /// expected by POSIX, from a hash map plus options. |
| 357 | 381 | pub fn createPosixBlock( |
lib/std/zig.zig+95-18| ... | ... | @@ -33,6 +33,7 @@ pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig"); |
| 33 | 33 | pub const LibCInstallation = @import("zig/LibCInstallation.zig"); |
| 34 | 34 | pub const WindowsSdk = @import("zig/WindowsSdk.zig"); |
| 35 | 35 | pub const LibCDirs = @import("zig/LibCDirs.zig"); |
| 36 | pub const PkgConfig = @import("zig/PkgConfig.zig"); | |
| 36 | 37 | pub const target = @import("zig/target.zig"); |
| 37 | 38 | pub const llvm = @import("zig/llvm.zig"); |
| 38 | 39 | |
| ... | ... | @@ -146,7 +147,10 @@ pub fn lineDelta(source: []const u8, start: usize, end: usize) isize { |
| 146 | 147 | |
| 147 | 148 | pub const BinNameOptions = struct { |
| 148 | 149 | root_name: []const u8, |
| 149 | target: *const std.Target, | |
| 150 | cpu_arch: std.Target.Cpu.Arch, | |
| 151 | os_tag: std.Target.Os.Tag, | |
| 152 | ofmt: std.Target.ObjectFormat, | |
| 153 | abi: std.Target.Abi, | |
| 150 | 154 | output_mode: std.builtin.OutputMode, |
| 151 | 155 | link_mode: ?std.builtin.LinkMode = null, |
| 152 | 156 | version: ?std.SemanticVersion = null, |
| ... | ... | @@ -155,10 +159,12 @@ pub const BinNameOptions = struct { |
| 155 | 159 | /// Returns the standard file system basename of a binary generated by the Zig compiler. |
| 156 | 160 | pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 { |
| 157 | 161 | const root_name = options.root_name; |
| 158 | const t = options.target; | |
| 159 | switch (t.ofmt) { | |
| 162 | switch (options.ofmt) { | |
| 160 | 163 | .coff => switch (options.output_mode) { |
| 161 | .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }), | |
| 164 | .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ | |
| 165 | root_name, | |
| 166 | options.os_tag.exeFileExt(options.cpu_arch), | |
| 167 | }), | |
| 162 | 168 | .Lib => { |
| 163 | 169 | const suffix = switch (options.link_mode orelse .static) { |
| 164 | 170 | .static => ".lib", |
| ... | ... | @@ -173,16 +179,16 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe |
| 173 | 179 | .Lib => { |
| 174 | 180 | switch (options.link_mode orelse .static) { |
| 175 | 181 | .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ |
| 176 | t.libPrefix(), root_name, | |
| 182 | options.os_tag.libPrefix(options.abi), root_name, | |
| 177 | 183 | }), |
| 178 | 184 | .dynamic => { |
| 179 | 185 | if (options.version) |ver| { |
| 180 | 186 | return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{ |
| 181 | t.libPrefix(), root_name, ver.major, ver.minor, ver.patch, | |
| 187 | options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch, | |
| 182 | 188 | }); |
| 183 | 189 | } else { |
| 184 | 190 | return std.fmt.allocPrint(allocator, "{s}{s}.so", .{ |
| 185 | t.libPrefix(), root_name, | |
| 191 | options.os_tag.libPrefix(options.abi), root_name, | |
| 186 | 192 | }); |
| 187 | 193 | } |
| 188 | 194 | }, |
| ... | ... | @@ -195,16 +201,16 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe |
| 195 | 201 | .Lib => { |
| 196 | 202 | switch (options.link_mode orelse .static) { |
| 197 | 203 | .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ |
| 198 | t.libPrefix(), root_name, | |
| 204 | options.os_tag.libPrefix(options.abi), root_name, | |
| 199 | 205 | }), |
| 200 | 206 | .dynamic => { |
| 201 | 207 | if (options.version) |ver| { |
| 202 | 208 | return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{ |
| 203 | t.libPrefix(), root_name, ver.major, ver.minor, ver.patch, | |
| 209 | options.os_tag.libPrefix(options.abi), root_name, ver.major, ver.minor, ver.patch, | |
| 204 | 210 | }); |
| 205 | 211 | } else { |
| 206 | 212 | return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{ |
| 207 | t.libPrefix(), root_name, | |
| 213 | options.os_tag.libPrefix(options.abi), root_name, | |
| 208 | 214 | }); |
| 209 | 215 | } |
| 210 | 216 | }, |
| ... | ... | @@ -213,11 +219,14 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe |
| 213 | 219 | .Obj => return std.fmt.allocPrint(allocator, "{s}.o", .{root_name}), |
| 214 | 220 | }, |
| 215 | 221 | .wasm => switch (options.output_mode) { |
| 216 | .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, t.exeFileExt() }), | |
| 222 | .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ | |
| 223 | root_name, | |
| 224 | options.os_tag.exeFileExt(options.cpu_arch), | |
| 225 | }), | |
| 217 | 226 | .Lib => { |
| 218 | 227 | switch (options.link_mode orelse .static) { |
| 219 | 228 | .static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ |
| 220 | t.libPrefix(), root_name, | |
| 229 | options.os_tag.libPrefix(options.abi), root_name, | |
| 221 | 230 | }), |
| 222 | 231 | .dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}), |
| 223 | 232 | } |
| ... | ... | @@ -231,10 +240,10 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe |
| 231 | 240 | .plan9 => switch (options.output_mode) { |
| 232 | 241 | .Exe => return allocator.dupe(u8, root_name), |
| 233 | 242 | .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ |
| 234 | root_name, t.ofmt.fileExt(t.cpu.arch), | |
| 243 | root_name, options.ofmt.fileExt(options.cpu_arch), | |
| 235 | 244 | }), |
| 236 | 245 | .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ |
| 237 | t.libPrefix(), root_name, | |
| 246 | options.os_tag.libPrefix(options.abi), root_name, | |
| 238 | 247 | }), |
| 239 | 248 | }, |
| 240 | 249 | } |
| ... | ... | @@ -374,9 +383,9 @@ pub const Subsystem = enum { |
| 374 | 383 | pub const EfiRuntimeDriver: Subsystem = .efi_runtime_driver; |
| 375 | 384 | }; |
| 376 | 385 | |
| 377 | pub const CompressDebugSections = enum { none, zlib, zstd }; | |
| 386 | pub const CompressDebugSections = enum(u2) { none, zlib, zstd }; | |
| 378 | 387 | |
| 379 | pub const RcIncludes = enum { | |
| 388 | pub const RcIncludes = enum(u2) { | |
| 380 | 389 | /// Use MSVC if available, fall back to MinGW. |
| 381 | 390 | any, |
| 382 | 391 | /// Use MSVC include paths (MSVC install + Windows SDK, must be present on the system). |
| ... | ... | @@ -672,7 +681,7 @@ pub fn putAstErrorsIntoBundle( |
| 672 | 681 | |
| 673 | 682 | pub fn resolveTargetQueryOrFatal(io: Io, target_query: std.Target.Query) std.Target { |
| 674 | 683 | return std.zig.system.resolveTargetQuery(io, target_query) catch |err| |
| 675 | std.process.fatal("unable to resolve target: {s}", .{@errorName(err)}); | |
| 684 | std.process.fatal("unable to resolve target: {t}", .{err}); | |
| 676 | 685 | } |
| 677 | 686 | |
| 678 | 687 | pub fn parseTargetQueryOrReportFatalError( |
| ... | ... | @@ -747,7 +756,6 @@ pub const EnvVar = enum { |
| 747 | 756 | ZIG_LOCAL_PKG_DIR, |
| 748 | 757 | ZIG_LIB_DIR, |
| 749 | 758 | ZIG_LIBC, |
| 750 | ZIG_BUILD_RUNNER, | |
| 751 | 759 | ZIG_BUILD_ERROR_STYLE, |
| 752 | 760 | ZIG_BUILD_MULTILINE_ERRORS, |
| 753 | 761 | ZIG_VERBOSE_LINK, |
| ... | ... | @@ -764,6 +772,7 @@ pub const EnvVar = enum { |
| 764 | 772 | CPLUS_INCLUDE_PATH, |
| 765 | 773 | LIBRARY_PATH, |
| 766 | 774 | CC, |
| 775 | PKG_CONFIG, | |
| 767 | 776 | |
| 768 | 777 | // Terminal integration |
| 769 | 778 | NO_COLOR, |
| ... | ... | @@ -1157,6 +1166,74 @@ pub const ClangCliParam = struct { |
| 1157 | 1166 | } |
| 1158 | 1167 | }; |
| 1159 | 1168 | |
| 1169 | pub const AllocPrintCmdOptions = struct { | |
| 1170 | cwd: ?[]const u8 = null, | |
| 1171 | parent_env: ?*const std.process.Environ.Map = null, | |
| 1172 | child_env: ?*const std.process.Environ.Map = null, | |
| 1173 | }; | |
| 1174 | ||
| 1175 | pub fn allocPrintCmd(gpa: Allocator, argv: []const []const u8, options: AllocPrintCmdOptions) Allocator.Error![]u8 { | |
| 1176 | const shell = struct { | |
| 1177 | fn escape(writer: *Io.Writer, string: []const u8, is_argv0: bool) !void { | |
| 1178 | for (string) |c| { | |
| 1179 | if (switch (c) { | |
| 1180 | else => true, | |
| 1181 | '%', '+'...':', '@'...'Z', '_', 'a'...'z' => false, | |
| 1182 | '=' => is_argv0, | |
| 1183 | }) break; | |
| 1184 | } else return writer.writeAll(string); | |
| 1185 | ||
| 1186 | try writer.writeByte('"'); | |
| 1187 | for (string) |c| { | |
| 1188 | if (switch (c) { | |
| 1189 | std.ascii.control_code.nul => break, | |
| 1190 | '!', '"', '$', '\\', '`' => true, | |
| 1191 | else => !std.ascii.isPrint(c), | |
| 1192 | }) try writer.writeByte('\\'); | |
| 1193 | switch (c) { | |
| 1194 | std.ascii.control_code.nul => unreachable, | |
| 1195 | std.ascii.control_code.bel => try writer.writeByte('a'), | |
| 1196 | std.ascii.control_code.bs => try writer.writeByte('b'), | |
| 1197 | std.ascii.control_code.ht => try writer.writeByte('t'), | |
| 1198 | std.ascii.control_code.lf => try writer.writeByte('n'), | |
| 1199 | std.ascii.control_code.vt => try writer.writeByte('v'), | |
| 1200 | std.ascii.control_code.ff => try writer.writeByte('f'), | |
| 1201 | std.ascii.control_code.cr => try writer.writeByte('r'), | |
| 1202 | std.ascii.control_code.esc => try writer.writeByte('E'), | |
| 1203 | ' '...'~' => try writer.writeByte(c), | |
| 1204 | else => try writer.print("{o:0>3}", .{c}), | |
| 1205 | } | |
| 1206 | } | |
| 1207 | try writer.writeByte('"'); | |
| 1208 | } | |
| 1209 | }; | |
| 1210 | ||
| 1211 | var aw: Io.Writer.Allocating = .init(gpa); | |
| 1212 | defer aw.deinit(); | |
| 1213 | const writer = &aw.writer; | |
| 1214 | if (options.cwd) |path| { | |
| 1215 | writer.print("cd {s} && ", .{path}) catch return error.OutOfMemory; | |
| 1216 | } | |
| 1217 | if (options.child_env) |child_env| { | |
| 1218 | for (child_env.keys(), child_env.values()) |key, value| { | |
| 1219 | if (options.parent_env) |parent_env| { | |
| 1220 | if (parent_env.get(key)) |process_value| { | |
| 1221 | if (std.mem.eql(u8, value, process_value)) continue; | |
| 1222 | } | |
| 1223 | } | |
| 1224 | writer.print("{s}=", .{key}) catch return error.OutOfMemory; | |
| 1225 | shell.escape(writer, value, false) catch return error.OutOfMemory; | |
| 1226 | writer.writeByte(' ') catch return error.OutOfMemory; | |
| 1227 | } | |
| 1228 | } | |
| 1229 | shell.escape(writer, argv[0], true) catch return error.OutOfMemory; | |
| 1230 | for (argv[1..]) |arg| { | |
| 1231 | writer.writeByte(' ') catch return error.OutOfMemory; | |
| 1232 | shell.escape(writer, arg, false) catch return error.OutOfMemory; | |
| 1233 | } | |
| 1234 | return aw.toOwnedSlice(); | |
| 1235 | } | |
| 1236 | ||
| 1160 | 1237 | test { |
| 1161 | 1238 | _ = Ast; |
| 1162 | 1239 | _ = AstRlAnnotate; |
lib/std/zig/LibCInstallation.zig+16-2| ... | ... | @@ -13,6 +13,7 @@ const Target = std.Target; |
| 13 | 13 | const fs = std.fs; |
| 14 | 14 | const Allocator = std.mem.Allocator; |
| 15 | 15 | const Path = std.Build.Cache.Path; |
| 16 | const Cache = std.Build.Cache; | |
| 16 | 17 | const log = std.log.scoped(.libc_installation); |
| 17 | 18 | const Environ = std.process.Environ; |
| 18 | 19 | |
| ... | ... | @@ -990,7 +991,7 @@ pub fn resolveCrtPaths( |
| 990 | 991 | target: *const std.Target, |
| 991 | 992 | ) error{ OutOfMemory, LibCInstallationMissingCrtDir }!CrtPaths { |
| 992 | 993 | const crt_dir_path: Path = .{ |
| 993 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 994 | .root_dir = Cache.Directory.cwd(), | |
| 994 | 995 | .sub_path = lci.crt_dir orelse return error.LibCInstallationMissingCrtDir, |
| 995 | 996 | }; |
| 996 | 997 | switch (target.os.tag) { |
| ... | ... | @@ -1016,7 +1017,7 @@ pub fn resolveCrtPaths( |
| 1016 | 1017 | }, |
| 1017 | 1018 | .haiku, .serenity => { |
| 1018 | 1019 | const gcc_dir_path: Path = .{ |
| 1019 | .root_dir = std.Build.Cache.Directory.cwd(), | |
| 1020 | .root_dir = Cache.Directory.cwd(), | |
| 1020 | 1021 | .sub_path = lci.gcc_dir orelse return error.LibCInstallationMissingCrtDir, |
| 1021 | 1022 | }; |
| 1022 | 1023 | return .{ |
| ... | ... | @@ -1038,3 +1039,16 @@ pub fn resolveCrtPaths( |
| 1038 | 1039 | }, |
| 1039 | 1040 | } |
| 1040 | 1041 | } |
| 1042 | ||
| 1043 | pub fn addToHash(opt_lci: ?*const LibCInstallation, hh: *Cache.HashHelper, abi: std.Target.Abi) void { | |
| 1044 | const lci = opt_lci orelse return hh.add(false); | |
| 1045 | hh.add(true); | |
| 1046 | hh.addOptionalBytes(lci.crt_dir); | |
| 1047 | switch (abi) { | |
| 1048 | .msvc, .itanium => { | |
| 1049 | hh.addOptionalBytes(lci.msvc_lib_dir); | |
| 1050 | hh.addOptionalBytes(lci.kernel32_lib_dir); | |
| 1051 | }, | |
| 1052 | else => {}, | |
| 1053 | } | |
| 1054 | } |
lib/std/zig/PkgConfig.zig created+146| ... | ... | @@ -0,0 +1,146 @@ |
| 1 | //! The more reusable pieces of the build system's pkg-config integration logic. | |
| 2 | const PkgConfig = @This(); | |
| 3 | ||
| 4 | const std = @import("../std.zig"); | |
| 5 | const mem = std.mem; | |
| 6 | const Allocator = std.mem.Allocator; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | all: []const Pkg, | |
| 10 | ||
| 11 | pub const Pkg = struct { | |
| 12 | name: []const u8, | |
| 13 | desc: []const u8, | |
| 14 | }; | |
| 15 | ||
| 16 | pub const InitError = Allocator.Error || error{InvalidPkgConfigOutput}; | |
| 17 | ||
| 18 | pub const Diagnostic = struct { | |
| 19 | invalid_line_index: usize, | |
| 20 | invalid_line: []const u8, | |
| 21 | }; | |
| 22 | ||
| 23 | /// Parses the output of `pkg-config --list-all`. | |
| 24 | pub fn init(arena: Allocator, stdout: []const u8, diagnostic: ?*Diagnostic) InitError!PkgConfig { | |
| 25 | var list: std.ArrayList(Pkg) = .empty; | |
| 26 | var line_it = mem.tokenizeAny(u8, stdout, "\r\n"); | |
| 27 | var line_index: usize = 0; | |
| 28 | while (line_it.next()) |line| : (line_index += 1) { | |
| 29 | if (mem.trim(u8, line, " \t").len == 0) continue; | |
| 30 | var tok_it = mem.tokenizeAny(u8, line, " \t"); | |
| 31 | try list.append(arena, .{ | |
| 32 | .name = tok_it.next() orelse { | |
| 33 | if (diagnostic) |d| d.* = .{ | |
| 34 | .invalid_line_index = line_index, | |
| 35 | .invalid_line = line, | |
| 36 | }; | |
| 37 | return error.InvalidPkgConfigOutput; | |
| 38 | }, | |
| 39 | .desc = tok_it.rest(), | |
| 40 | }); | |
| 41 | } | |
| 42 | try list.shrinkToLen(arena); | |
| 43 | return .{ .all = list.toOwnedSliceAssert() }; | |
| 44 | } | |
| 45 | ||
| 46 | // Maps the library name to pkg config name. Unfortunately, there are several | |
| 47 | // examples where this is not straightforward: | |
| 48 | // * -lSDL2 -> pkg-config sdl2 | |
| 49 | // * -lgdk-3 -> pkg-config gdk-3.0 | |
| 50 | // * -latk-1.0 -> pkg-config atk | |
| 51 | // * -lpulse -> pkg-config libpulse | |
| 52 | pub fn find(pc: *const PkgConfig, lib_name: []const u8) ?usize { | |
| 53 | const all = pc.all; | |
| 54 | ||
| 55 | // Exact match means instant winner. | |
| 56 | for (all, 0..) |pkg, i| { | |
| 57 | if (mem.eql(u8, pkg.name, lib_name)) | |
| 58 | return i; | |
| 59 | } | |
| 60 | ||
| 61 | // Next we'll try ignoring case. | |
| 62 | for (all, 0..) |pkg, i| { | |
| 63 | if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) | |
| 64 | return i; | |
| 65 | } | |
| 66 | ||
| 67 | // Prefixed "lib" or suffixed ".0". | |
| 68 | for (all, 0..) |pkg, i| { | |
| 69 | if (std.ascii.findIgnoreCase(pkg.name, lib_name)) |pos| { | |
| 70 | const prefix = pkg.name[0..pos]; | |
| 71 | const suffix = pkg.name[pos + lib_name.len ..]; | |
| 72 | if (prefix.len > 0 and !mem.eql(u8, prefix, "lib")) continue; | |
| 73 | if (suffix.len > 0 and !mem.eql(u8, suffix, ".0")) continue; | |
| 74 | return i; | |
| 75 | } | |
| 76 | } | |
| 77 | ||
| 78 | // Trimming "-1.0". | |
| 79 | if (mem.cutSuffix(u8, lib_name, "-1.0")) |trimmed| { | |
| 80 | for (all, 0..) |pkg, i| { | |
| 81 | if (std.ascii.eqlIgnoreCase(pkg.name, trimmed)) { | |
| 82 | return i; | |
| 83 | } | |
| 84 | } | |
| 85 | } | |
| 86 | ||
| 87 | return null; | |
| 88 | } | |
| 89 | ||
| 90 | pub fn exe(environ_map: *const std.process.Environ.Map) []const u8 { | |
| 91 | return std.zig.EnvVar.PKG_CONFIG.get(environ_map) orelse "pkg-config"; | |
| 92 | } | |
| 93 | ||
| 94 | pub const Parsed = struct { | |
| 95 | cflags: []const []const u8, | |
| 96 | libs: []const []const u8, | |
| 97 | unknown_flags: []const []const u8, | |
| 98 | }; | |
| 99 | ||
| 100 | pub const ParseError = Allocator.Error || error{InvalidPkgConfigOutput}; | |
| 101 | ||
| 102 | /// Parses the output of `pkg-config [name] --cflags --libs`. | |
| 103 | pub fn parse(arena: Allocator, stdout: []const u8) ParseError!Parsed { | |
| 104 | var zig_cflags: std.ArrayList([]const u8) = .empty; | |
| 105 | var zig_libs: std.ArrayList([]const u8) = .empty; | |
| 106 | var unknown_flags: std.ArrayList([]const u8) = .empty; | |
| 107 | var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t"); | |
| 108 | ||
| 109 | while (arg_it.next()) |arg| { | |
| 110 | if (mem.eql(u8, arg, "-I")) { | |
| 111 | const dir = arg_it.next() orelse return error.InvalidPkgConfigOutput; | |
| 112 | try zig_cflags.appendSlice(arena, &.{ "-I", dir }); | |
| 113 | } else if (mem.startsWith(u8, arg, "-I")) { | |
| 114 | try zig_cflags.append(arena, arg); | |
| 115 | } else if (mem.eql(u8, arg, "-L")) { | |
| 116 | const dir = arg_it.next() orelse return error.InvalidPkgConfigOutput; | |
| 117 | try zig_libs.appendSlice(arena, &.{ "-L", dir }); | |
| 118 | } else if (mem.startsWith(u8, arg, "-L")) { | |
| 119 | try zig_libs.append(arena, arg); | |
| 120 | } else if (mem.eql(u8, arg, "-l")) { | |
| 121 | const lib = arg_it.next() orelse return error.InvalidPkgConfigOutput; | |
| 122 | try zig_libs.appendSlice(arena, &.{ "-l", lib }); | |
| 123 | } else if (mem.startsWith(u8, arg, "-l")) { | |
| 124 | try zig_libs.append(arena, arg); | |
| 125 | } else if (mem.eql(u8, arg, "-D")) { | |
| 126 | const macro = arg_it.next() orelse return error.InvalidPkgConfigOutput; | |
| 127 | try zig_cflags.appendSlice(arena, &.{ "-D", macro }); | |
| 128 | } else if (mem.startsWith(u8, arg, "-D")) { | |
| 129 | try zig_cflags.append(arena, arg); | |
| 130 | } else if (mem.cutPrefix(u8, arg, "-Wl,-rpath,")) |rest| { | |
| 131 | try zig_cflags.appendSlice(arena, &.{ "-rpath", rest }); | |
| 132 | } else { | |
| 133 | try unknown_flags.append(arena, arg); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | try zig_cflags.shrinkToLen(arena); | |
| 138 | try zig_libs.shrinkToLen(arena); | |
| 139 | try unknown_flags.shrinkToLen(arena); | |
| 140 | ||
| 141 | return .{ | |
| 142 | .cflags = zig_cflags.toOwnedSliceAssert(), | |
| 143 | .libs = zig_libs.toOwnedSliceAssert(), | |
| 144 | .unknown_flags = unknown_flags.toOwnedSliceAssert(), | |
| 145 | }; | |
| 146 | } |
lib/std/zig/system.zig+16-17| ... | ... | @@ -28,6 +28,8 @@ pub const Executor = union(enum) { |
| 28 | 28 | }; |
| 29 | 29 | |
| 30 | 30 | pub const GetExternalExecutorOptions = struct { |
| 31 | host_cpu_arch: std.Target.Cpu.Arch, | |
| 32 | host_os_tag: std.Target.Os.Tag, | |
| 31 | 33 | allow_darling: bool = true, |
| 32 | 34 | allow_qemu: bool = true, |
| 33 | 35 | allow_rosetta: bool = true, |
| ... | ... | @@ -39,24 +41,21 @@ pub const GetExternalExecutorOptions = struct { |
| 39 | 41 | |
| 40 | 42 | /// Return whether or not the given host is capable of running executables of |
| 41 | 43 | /// the other target. |
| 42 | pub fn getExternalExecutor( | |
| 43 | io: Io, | |
| 44 | host: *const std.Target, | |
| 45 | candidate: *const std.Target, | |
| 46 | options: GetExternalExecutorOptions, | |
| 47 | ) Executor { | |
| 48 | const os_match = host.os.tag == candidate.os.tag; | |
| 44 | pub fn getExternalExecutor(io: Io, candidate: *const std.Target, options: GetExternalExecutorOptions) Executor { | |
| 45 | const host_os_tag = options.host_os_tag; | |
| 46 | const host_cpu_arch = options.host_cpu_arch; | |
| 47 | const os_match = host_os_tag == candidate.os.tag; | |
| 49 | 48 | const cpu_ok = cpu_ok: { |
| 50 | if (host.cpu.arch == candidate.cpu.arch) | |
| 49 | if (host_cpu_arch == candidate.cpu.arch) | |
| 51 | 50 | break :cpu_ok true; |
| 52 | 51 | |
| 53 | if (host.cpu.arch == .x86_64 and candidate.cpu.arch == .x86) | |
| 52 | if (host_cpu_arch == .x86_64 and candidate.cpu.arch == .x86) | |
| 54 | 53 | break :cpu_ok true; |
| 55 | 54 | |
| 56 | if (host.cpu.arch == .aarch64 and candidate.cpu.arch == .arm) | |
| 55 | if (host_cpu_arch == .aarch64 and candidate.cpu.arch == .arm) | |
| 57 | 56 | break :cpu_ok true; |
| 58 | 57 | |
| 59 | if (host.cpu.arch == .aarch64_be and candidate.cpu.arch == .armeb) | |
| 58 | if (host_cpu_arch == .aarch64_be and candidate.cpu.arch == .armeb) | |
| 60 | 59 | break :cpu_ok true; |
| 61 | 60 | |
| 62 | 61 | // TODO additionally detect incompatible CPU features. |
| ... | ... | @@ -83,7 +82,7 @@ pub fn getExternalExecutor( |
| 83 | 82 | // If the OS match and OS is macOS and CPU is arm64, we can use Rosetta 2 |
| 84 | 83 | // to emulate the foreign architecture. |
| 85 | 84 | if (options.allow_rosetta and os_match and |
| 86 | (host.os.tag == .maccatalyst or host.os.tag == .macos) and host.cpu.arch == .aarch64) | |
| 85 | (host_os_tag == .maccatalyst or host_os_tag == .macos) and host_cpu_arch == .aarch64) | |
| 87 | 86 | { |
| 88 | 87 | switch (candidate.cpu.arch) { |
| 89 | 88 | .x86_64 => return .rosetta, |
| ... | ... | @@ -173,13 +172,13 @@ pub fn getExternalExecutor( |
| 173 | 172 | .windows => { |
| 174 | 173 | if (options.allow_wine) { |
| 175 | 174 | const wine_supported = switch (candidate.cpu.arch) { |
| 176 | .thumb => switch (host.cpu.arch) { | |
| 175 | .thumb => switch (host_cpu_arch) { | |
| 177 | 176 | .arm, .thumb, .aarch64 => true, |
| 178 | 177 | else => false, |
| 179 | 178 | }, |
| 180 | .aarch64 => host.cpu.arch == .aarch64, | |
| 181 | .x86 => host.cpu.arch.isX86(), | |
| 182 | .x86_64 => host.cpu.arch == .x86_64, | |
| 179 | .aarch64 => host_cpu_arch == .aarch64, | |
| 180 | .x86 => host_cpu_arch.isX86(), | |
| 181 | .x86_64 => host_cpu_arch == .x86_64, | |
| 183 | 182 | else => false, |
| 184 | 183 | }; |
| 185 | 184 | return if (wine_supported) .{ .wine = "wine" } else bad_result; |
| ... | ... | @@ -191,7 +190,7 @@ pub fn getExternalExecutor( |
| 191 | 190 | // This check can be loosened once darling adds a QEMU-based emulation |
| 192 | 191 | // layer for non-host architectures: |
| 193 | 192 | // https://github.com/darlinghq/darling/issues/863 |
| 194 | if (candidate.cpu.arch != host.cpu.arch) { | |
| 193 | if (candidate.cpu.arch != host_cpu_arch) { | |
| 195 | 194 | return bad_result; |
| 196 | 195 | } |
| 197 | 196 | return .{ .darling = "darling" }; |
lib/std/zon/Serializer.zig+6-2| ... | ... | @@ -122,7 +122,7 @@ pub fn valueMaxDepth(self: *Serializer, val: anytype, options: ValueOptions, dep |
| 122 | 122 | |
| 123 | 123 | /// Serialize a value, similar to `serializeArbitraryDepth`. |
| 124 | 124 | pub fn valueArbitraryDepth(self: *Serializer, val: anytype, options: ValueOptions) Error!void { |
| 125 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 125 | comptime assertCanSerializeType(@TypeOf(val)); | |
| 126 | 126 | switch (@typeInfo(@TypeOf(val))) { |
| 127 | 127 | .int, .comptime_int => if (options.emit_codepoint_literals.emitAsCodepoint(val)) |c| { |
| 128 | 128 | self.codePoint(c) catch |err| switch (err) { |
| ... | ... | @@ -321,7 +321,7 @@ pub fn tupleArbitraryDepth( |
| 321 | 321 | } |
| 322 | 322 | |
| 323 | 323 | fn tupleImpl(self: *Serializer, val: anytype, options: ValueOptions) Error!void { |
| 324 | comptime assert(canSerializeType(@TypeOf(val))); | |
| 324 | comptime assertCanSerializeType(@TypeOf(val)); | |
| 325 | 325 | switch (@typeInfo(@TypeOf(val))) { |
| 326 | 326 | .@"struct" => { |
| 327 | 327 | var container = try self.beginTuple(.{ .whitespace_style = .{ .fields = val.len } }); |
| ... | ... | @@ -814,6 +814,10 @@ test checkValueDepth { |
| 814 | 814 | try expectValueDepthEquals(3, @as([]const []const u8, &.{&.{ 1, 2, 3 }})); |
| 815 | 815 | } |
| 816 | 816 | |
| 817 | inline fn assertCanSerializeType(T: type) void { | |
| 818 | if (!canSerializeType(T)) @compileError("cannot serialize: " ++ @typeName(T)); | |
| 819 | } | |
| 820 | ||
| 817 | 821 | inline fn canSerializeType(T: type) bool { |
| 818 | 822 | comptime return canSerializeTypeInner(T, &.{}, false); |
| 819 | 823 | } |
src/Compilation.zig+14-17| ... | ... | @@ -752,13 +752,10 @@ pub const Directories = struct { |
| 752 | 752 | else => []const u8, |
| 753 | 753 | }, |
| 754 | 754 | environ_map: *const std.process.Environ.Map, |
| 755 | cwd: []const u8, | |
| 755 | 756 | ) Directories { |
| 756 | 757 | const wasi = builtin.target.os.tag == .wasi; |
| 757 | 758 | |
| 758 | const cwd = introspect.getResolvedCwd(io, arena) catch |err| { | |
| 759 | fatal("unable to get cwd: {t}", .{err}); | |
| 760 | }; | |
| 761 | ||
| 762 | 759 | const zig_lib: Cache.Directory = d: { |
| 763 | 760 | if (override_zig_lib) |path| break :d openUnresolved(arena, io, cwd, path, .@"zig lib"); |
| 764 | 761 | if (wasi) break :d getPreopen(preopens, "/lib"); |
| ... | ... | @@ -1750,9 +1747,13 @@ pub const CreateOptions = struct { |
| 1750 | 1747 | .no => return null, |
| 1751 | 1748 | .yes_cache => { |
| 1752 | 1749 | assert(opts.cache_mode != .none); |
| 1750 | const target = &opts.root_mod.resolved_target.result; | |
| 1753 | 1751 | return try ea.cacheName(arena, .{ |
| 1754 | 1752 | .root_name = opts.root_name, |
| 1755 | .target = &opts.root_mod.resolved_target.result, | |
| 1753 | .cpu_arch = target.cpu.arch, | |
| 1754 | .os_tag = target.os.tag, | |
| 1755 | .ofmt = target.ofmt, | |
| 1756 | .abi = target.abi, | |
| 1756 | 1757 | .output_mode = opts.config.output_mode, |
| 1757 | 1758 | .link_mode = opts.config.link_mode, |
| 1758 | 1759 | .version = opts.version, |
| ... | ... | @@ -3236,9 +3237,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE |
| 3236 | 3237 | } |
| 3237 | 3238 | |
| 3238 | 3239 | // Failure here only means an unnecessary cache miss. |
| 3239 | man.writeManifest() catch |err| { | |
| 3240 | log.warn("failed to write cache manifest: {s}", .{@errorName(err)}); | |
| 3241 | }; | |
| 3240 | man.writeManifest() catch |err| log.warn("failed to write cache manifest: {t}", .{err}); | |
| 3242 | 3241 | |
| 3243 | 3242 | assert(whole.lock == null); |
| 3244 | 3243 | whole.lock = man.toOwnedLock(); |
| ... | ... | @@ -3528,14 +3527,7 @@ fn addNonIncrementalStuffToCacheManifest( |
| 3528 | 3527 | man.hash.addListOfBytes(opts.rpath_list); |
| 3529 | 3528 | man.hash.addListOfBytes(opts.symbol_wrap_set.keys()); |
| 3530 | 3529 | if (comp.config.link_libc) { |
| 3531 | man.hash.add(comp.libc_installation != null); | |
| 3532 | if (comp.libc_installation) |libc_installation| { | |
| 3533 | man.hash.addOptionalBytes(libc_installation.crt_dir); | |
| 3534 | if (target.abi == .msvc or target.abi == .itanium) { | |
| 3535 | man.hash.addOptionalBytes(libc_installation.msvc_lib_dir); | |
| 3536 | man.hash.addOptionalBytes(libc_installation.kernel32_lib_dir); | |
| 3537 | } | |
| 3538 | } | |
| 3530 | LibCInstallation.addToHash(comp.libc_installation, &man.hash, target.abi); | |
| 3539 | 3531 | man.hash.addOptionalBytes(target.dynamic_linker.get()); |
| 3540 | 3532 | } |
| 3541 | 3533 | man.hash.add(opts.repro); |
| ... | ... | @@ -7473,9 +7465,14 @@ pub fn build_crt_file( |
| 7473 | 7465 | defer arena_allocator.deinit(); |
| 7474 | 7466 | const arena = arena_allocator.allocator(); |
| 7475 | 7467 | |
| 7468 | const target = &comp.root_mod.resolved_target.result; | |
| 7469 | ||
| 7476 | 7470 | const basename = try std.zig.binNameAlloc(gpa, .{ |
| 7477 | 7471 | .root_name = root_name, |
| 7478 | .target = &comp.root_mod.resolved_target.result, | |
| 7472 | .cpu_arch = target.cpu.arch, | |
| 7473 | .os_tag = target.os.tag, | |
| 7474 | .ofmt = target.ofmt, | |
| 7475 | .abi = target.abi, | |
| 7479 | 7476 | .output_mode = output_mode, |
| 7480 | 7477 | }); |
| 7481 | 7478 |
src/Package/Fetch.zig+2-2| ... | ... | @@ -782,7 +782,7 @@ fn runResource( |
| 782 | 782 | f.package_root = try ls.pkg_root.join(arena, computed_package_hash.toSlice()); |
| 783 | 783 | renameTmpIntoCache(io, package_sub_path, f.package_root) catch |err| { |
| 784 | 784 | try eb.addRootErrorMessage(.{ .msg = try eb.printString( |
| 785 | "unable to rename temporary directory {f} into package cache directory {f}: {t}", | |
| 785 | "failed renaming temporary directory {f} into package cache directory {f}: {t}", | |
| 786 | 786 | .{ package_sub_path, f.package_root, err }, |
| 787 | 787 | ) }); |
| 788 | 788 | return error.FetchFailed; |
| ... | ... | @@ -802,7 +802,7 @@ fn runResource( |
| 802 | 802 | if (!package_sub_path.eql(tmp_directory_path)) { |
| 803 | 803 | tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) { |
| 804 | 804 | error.Canceled => |e| return e, |
| 805 | else => |e| log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }), | |
| 805 | else => |e| log.warn("failed deleting temporary directory {f}: {t}", .{ tmp_directory_path, e }), | |
| 806 | 806 | }; |
| 807 | 807 | } |
| 808 | 808 |
src/libs/libtsan.zig+4-1| ... | ... | @@ -41,7 +41,10 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo |
| 41 | 41 | const output_mode = .Lib; |
| 42 | 42 | const basename = try std.zig.binNameAlloc(arena, .{ |
| 43 | 43 | .root_name = root_name, |
| 44 | .target = target, | |
| 44 | .cpu_arch = target.cpu.arch, | |
| 45 | .os_tag = target.os.tag, | |
| 46 | .ofmt = target.ofmt, | |
| 47 | .abi = target.abi, | |
| 45 | 48 | .output_mode = output_mode, |
| 46 | 49 | .link_mode = link_mode, |
| 47 | 50 | }); |
src/main.zig+784-514| ... | ... | @@ -3166,6 +3166,8 @@ fn buildOutputType( |
| 3166 | 3166 | else => process.executablePathAlloc(io, arena) catch |err| fatal("unable to find zig self exe path: {t}", .{err}), |
| 3167 | 3167 | }; |
| 3168 | 3168 | |
| 3169 | const cwd_path = try introspect.getResolvedCwd(io, arena); | |
| 3170 | ||
| 3169 | 3171 | // This `init` calls `fatal` on error. |
| 3170 | 3172 | var dirs: Compilation.Directories = .init( |
| 3171 | 3173 | arena, |
| ... | ... | @@ -3182,6 +3184,7 @@ fn buildOutputType( |
| 3182 | 3184 | preopens, |
| 3183 | 3185 | self_exe_path, |
| 3184 | 3186 | environ_map, |
| 3187 | cwd_path, | |
| 3185 | 3188 | ); |
| 3186 | 3189 | defer dirs.deinit(io); |
| 3187 | 3190 | |
| ... | ... | @@ -3377,7 +3380,10 @@ fn buildOutputType( |
| 3377 | 3380 | .pch => try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}), |
| 3378 | 3381 | else => try std.zig.binNameAlloc(arena, .{ |
| 3379 | 3382 | .root_name = root_name, |
| 3380 | .target = target, | |
| 3383 | .cpu_arch = target.cpu.arch, | |
| 3384 | .os_tag = target.os.tag, | |
| 3385 | .ofmt = target.ofmt, | |
| 3386 | .abi = target.abi, | |
| 3381 | 3387 | .output_mode = create_module.resolved_options.output_mode, |
| 3382 | 3388 | .link_mode = create_module.resolved_options.link_mode, |
| 3383 | 3389 | .version = optional_version, |
| ... | ... | @@ -3675,26 +3681,16 @@ fn buildOutputType( |
| 3675 | 3681 | if (t.arch == target.cpu.arch and t.os == target.os.tag) { |
| 3676 | 3682 | // If there's a `glibc_min`, there's also an `os_ver`. |
| 3677 | 3683 | if (t.glibc_min) |glibc_min| { |
| 3678 | std.log.info("zig can provide libc for related target {s}-{s}.{f}-{s}.{d}.{d}", .{ | |
| 3679 | @tagName(t.arch), | |
| 3680 | @tagName(t.os), | |
| 3681 | t.os_ver.?, | |
| 3682 | @tagName(t.abi), | |
| 3683 | glibc_min.major, | |
| 3684 | glibc_min.minor, | |
| 3684 | std.log.info("zig can provide libc for related target {t}-{t}.{f}-{t}.{d}.{d}", .{ | |
| 3685 | t.arch, t.os, t.os_ver.?, t.abi, glibc_min.major, glibc_min.minor, | |
| 3685 | 3686 | }); |
| 3686 | 3687 | } else if (t.os_ver) |os_ver| { |
| 3687 | std.log.info("zig can provide libc for related target {s}-{s}.{f}-{s}", .{ | |
| 3688 | @tagName(t.arch), | |
| 3689 | @tagName(t.os), | |
| 3690 | os_ver, | |
| 3691 | @tagName(t.abi), | |
| 3688 | std.log.info("zig can provide libc for related target {t}-{t}.{f}-{t}", .{ | |
| 3689 | t.arch, t.os, os_ver, t.abi, | |
| 3692 | 3690 | }); |
| 3693 | 3691 | } else { |
| 3694 | std.log.info("zig can provide libc for related target {s}-{s}-{s}", .{ | |
| 3695 | @tagName(t.arch), | |
| 3696 | @tagName(t.os), | |
| 3697 | @tagName(t.abi), | |
| 3692 | std.log.info("zig can provide libc for related target {t}-{t}-{t}", .{ | |
| 3693 | t.arch, t.os, t.abi, | |
| 3698 | 3694 | }); |
| 3699 | 3695 | } |
| 3700 | 3696 | } |
| ... | ... | @@ -3703,7 +3699,7 @@ fn buildOutputType( |
| 3703 | 3699 | }, |
| 3704 | 3700 | else => fatal("{f}", .{create_diag}), |
| 3705 | 3701 | }, |
| 3706 | else => fatal("failed to create compilation: {s}", .{@errorName(err)}), | |
| 3702 | else => fatal("failed to create compilation: {t}", .{err}), | |
| 3707 | 3703 | }; |
| 3708 | 3704 | var comp_destroyed = false; |
| 3709 | 3705 | defer if (!comp_destroyed) comp.destroy(); |
| ... | ... | @@ -4936,16 +4932,25 @@ test sanitizeExampleName { |
| 4936 | 4932 | try std.testing.expectEqualStrings("test_project", try sanitizeExampleName(arena, "test project")); |
| 4937 | 4933 | } |
| 4938 | 4934 | |
| 4939 | fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, environ_map: *process.Environ.Map) !void { | |
| 4940 | dev.check(.build_command); | |
| 4941 | ||
| 4935 | fn cmdBuild( | |
| 4936 | gpa: Allocator, | |
| 4937 | arena: Allocator, | |
| 4938 | io: Io, | |
| 4939 | args: []const []const u8, | |
| 4940 | environ_map: *process.Environ.Map, | |
| 4941 | ) !void { | |
| 4942 | 4942 | var build_file: ?[]const u8 = null; |
| 4943 | 4943 | var override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map); |
| 4944 | 4944 | var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); |
| 4945 | 4945 | var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map); |
| 4946 | 4946 | var override_pkg_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_PKG_DIR.get(environ_map); |
| 4947 | var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(environ_map); | |
| 4948 | var child_argv: std.ArrayList([]const u8) = .empty; | |
| 4947 | var maker_optimize_mode: std.builtin.OptimizeMode = if (EnvVar.ZIG_DEBUG_CMD.isSet(environ_map)) | |
| 4948 | .Debug | |
| 4949 | else | |
| 4950 | .ReleaseSafe; | |
| 4951 | var configure_argv: std.ArrayList([]const u8) = .empty; | |
| 4952 | var make_argv: std.ArrayList([]const u8) = .empty; | |
| 4953 | var cached_passthru_configure: std.ArrayList(u32) = .empty; | |
| 4949 | 4954 | var forks: std.ArrayList(Fork) = .empty; |
| 4950 | 4955 | var reference_trace: ?u32 = null; |
| 4951 | 4956 | var debug_compile_errors = false; |
| ... | ... | @@ -4964,47 +4969,41 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, |
| 4964 | 4969 | var system_pkg_dir_path: ?[]const u8 = null; |
| 4965 | 4970 | var debug_target: ?[]const u8 = null; |
| 4966 | 4971 | var debug_libc_paths_file: ?[]const u8 = null; |
| 4967 | ||
| 4968 | const argv_index_exe = child_argv.items.len; | |
| 4969 | _ = try child_argv.addOne(arena); | |
| 4972 | var cache_poison: std.Build.Graph.CachePoison = .pure; | |
| 4970 | 4973 | |
| 4971 | 4974 | const self_exe_path = try process.executablePathAlloc(io, arena); |
| 4972 | try child_argv.append(arena, self_exe_path); | |
| 4975 | const default_seed = try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}); | |
| 4973 | 4976 | |
| 4974 | const argv_index_zig_lib_dir = child_argv.items.len; | |
| 4975 | _ = try child_argv.addOne(arena); | |
| 4977 | try configure_argv.ensureUnusedCapacity(arena, 16); | |
| 4978 | try make_argv.ensureUnusedCapacity(arena, 16); | |
| 4979 | try cached_passthru_configure.ensureUnusedCapacity(arena, 16); | |
| 4976 | 4980 | |
| 4977 | const argv_index_build_file = child_argv.items.len; | |
| 4978 | _ = try child_argv.addOne(arena); | |
| 4981 | _ = configure_argv.addOneAssumeCapacity(); // configurer executable | |
| 4982 | _ = make_argv.addOneAssumeCapacity(); // maker executable | |
| 4979 | 4983 | |
| 4980 | const argv_index_cache_dir = child_argv.items.len; | |
| 4981 | _ = try child_argv.addOne(arena); | |
| 4984 | make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig", self_exe_path }; | |
| 4985 | configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig", self_exe_path }; | |
| 4982 | 4986 | |
| 4983 | const argv_index_global_cache_dir = child_argv.items.len; | |
| 4984 | _ = try child_argv.addOne(arena); | |
| 4987 | make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--zig-lib-dir", undefined }; | |
| 4988 | const make_argv_index_zig_lib_dir = make_argv.items.len - 1; | |
| 4985 | 4989 | |
| 4986 | try child_argv.appendSlice(arena, &.{ | |
| 4987 | "--seed", | |
| 4988 | try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}), | |
| 4989 | }); | |
| 4990 | const argv_index_seed = child_argv.items.len - 1; | |
| 4991 | ||
| 4992 | // This parent process needs a way to obtain results from the configuration | |
| 4993 | // phase of the child process. In the future, the make phase will be | |
| 4994 | // executed in a separate process than the configure phase, and we can then | |
| 4995 | // use stdout from the configuration phase for this purpose. | |
| 4996 | // | |
| 4997 | // However, currently, both phases are in the same process, and Run Step | |
| 4998 | // provides API for making the runned subprocesses inherit stdout and stderr | |
| 4999 | // which means these streams are not available for passing metadata back | |
| 5000 | // to the parent. | |
| 5001 | // | |
| 5002 | // Until make and configure phases are separated into different processes, | |
| 5003 | // the strategy is to choose a temporary file name ahead of time, and then | |
| 5004 | // read this file in the parent to obtain the results, in the case the child | |
| 5005 | // exits with code 3. | |
| 5006 | const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64)); | |
| 5007 | try child_argv.append(arena, "-Z" ++ results_tmp_file_nonce); | |
| 4990 | make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--build-root", undefined }; | |
| 4991 | const make_argv_index_build_root = make_argv.items.len - 1; | |
| 4992 | ||
| 4993 | make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--local-cache", undefined }; | |
| 4994 | const make_argv_index_cache_dir = make_argv.items.len - 1; | |
| 4995 | ||
| 4996 | make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--global-cache", undefined }; | |
| 4997 | const make_argv_index_global_cache_dir = make_argv.items.len - 1; | |
| 4998 | ||
| 4999 | make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--configuration", undefined }; | |
| 5000 | const argv_index_configuration_file = make_argv.items.len - 1; | |
| 5001 | ||
| 5002 | make_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--seed", default_seed }; | |
| 5003 | const argv_index_seed = make_argv.items.len - 1; | |
| 5004 | ||
| 5005 | configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ "--build-root", undefined }; | |
| 5006 | const conf_argv_index_build_root = configure_argv.items.len - 1; | |
| 5008 | 5007 | |
| 5009 | 5008 | var color: Color = .auto; |
| 5010 | 5009 | var n_jobs: ?u32 = null; |
| ... | ... | @@ -5014,20 +5013,65 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, |
| 5014 | 5013 | while (i < args.len) : (i += 1) { |
| 5015 | 5014 | const arg = args[i]; |
| 5016 | 5015 | if (mem.startsWith(u8, arg, "-")) { |
| 5017 | if (mem.eql(u8, arg, "--build-file")) { | |
| 5016 | try configure_argv.ensureUnusedCapacity(arena, 2); | |
| 5017 | ||
| 5018 | if (mem.startsWith(u8, arg, "-D") or | |
| 5019 | mem.startsWith(u8, arg, "-fsys=") or | |
| 5020 | mem.startsWith(u8, arg, "-fno-sys=") or | |
| 5021 | mem.startsWith(u8, arg, "--release=") or | |
| 5022 | mem.eql(u8, arg, "--release")) | |
| 5023 | { | |
| 5024 | try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len)); | |
| 5025 | configure_argv.appendAssumeCapacity(arg); | |
| 5026 | continue; | |
| 5027 | } else if (mem.eql(u8, arg, "--system")) { | |
| 5018 | 5028 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 5019 | 5029 | i += 1; |
| 5020 | build_file = args[i]; | |
| 5030 | system_pkg_dir_path = args[i]; | |
| 5031 | ||
| 5032 | try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len)); | |
| 5033 | configure_argv.appendAssumeCapacity(arg); // Intentionally "--system" only; not the path. | |
| 5021 | 5034 | continue; |
| 5022 | } else if (mem.eql(u8, arg, "--zig-lib-dir")) { | |
| 5035 | } else if (mem.cutPrefix(u8, arg, "--color=")) |rest| { | |
| 5036 | color = std.meta.stringToEnum(Color, rest) orelse | |
| 5037 | fatal("expected --color=[auto|on|off]; found: {s}", .{arg}); | |
| 5038 | ||
| 5039 | try cached_passthru_configure.append(arena, @intCast(configure_argv.items.len)); | |
| 5040 | configure_argv.appendAssumeCapacity(arg); | |
| 5041 | continue; | |
| 5042 | } else if (mem.eql(u8, arg, "--cache-poison")) { | |
| 5043 | cache_poison = .poisoned; | |
| 5044 | configure_argv.appendAssumeCapacity("--cache-poison=poisoned"); | |
| 5045 | continue; | |
| 5046 | } else if (mem.cutPrefix(u8, arg, "--cache-poison=")) |rest| { | |
| 5047 | // Allow the configurer process to report parse failure. | |
| 5048 | if (std.meta.stringToEnum(std.Build.Graph.CachePoison, rest)) |poison| { | |
| 5049 | cache_poison = poison; | |
| 5050 | } | |
| 5051 | configure_argv.appendAssumeCapacity(arg); | |
| 5052 | continue; | |
| 5053 | } else if (mem.eql(u8, arg, "--verbose")) { | |
| 5054 | // Intentionally is added both to make and configure but | |
| 5055 | // does not go into the cache hash. | |
| 5056 | configure_argv.appendAssumeCapacity(arg); | |
| 5057 | } else if (mem.eql(u8, arg, "--search-prefix")) { | |
| 5023 | 5058 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 5024 | 5059 | i += 1; |
| 5025 | override_lib_dir = args[i]; | |
| 5060 | // This argument is cache poisonous: it does not go into | |
| 5061 | // the cache and configurer must set the poison bit when | |
| 5062 | // choosing to observe it. | |
| 5063 | configure_argv.addManyAsArrayAssumeCapacity(2).* = .{ arg, args[i] }; | |
| 5064 | (try make_argv.addManyAsArray(arena, 2)).* = .{ arg, args[i] }; | |
| 5065 | continue; | |
| 5066 | } else if (mem.eql(u8, arg, "--build-file")) { | |
| 5067 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); | |
| 5068 | i += 1; | |
| 5069 | build_file = args[i]; | |
| 5026 | 5070 | continue; |
| 5027 | } else if (mem.eql(u8, arg, "--build-runner")) { | |
| 5071 | } else if (mem.eql(u8, arg, "--zig-lib-dir")) { | |
| 5028 | 5072 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 5029 | 5073 | i += 1; |
| 5030 | override_build_runner = args[i]; | |
| 5074 | override_lib_dir = args[i]; | |
| 5031 | 5075 | continue; |
| 5032 | 5076 | } else if (mem.eql(u8, arg, "--cache-dir")) { |
| 5033 | 5077 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| ... | ... | @@ -5051,37 +5095,27 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, |
| 5051 | 5095 | } else if (mem.cutPrefix(u8, arg, "--fetch=")) |sub_arg| { |
| 5052 | 5096 | fetch_only = true; |
| 5053 | 5097 | fetch_mode = std.meta.stringToEnum(Package.Fetch.JobQueue.Mode, sub_arg) orelse |
| 5054 | fatal("expected [needed|all] after '--fetch=', found '{s}'", .{ | |
| 5055 | sub_arg, | |
| 5056 | }); | |
| 5098 | fatal("expected [needed|all] after '--fetch=', found '{s}'", .{sub_arg}); | |
| 5057 | 5099 | } else if (mem.cutPrefix(u8, arg, "--fork=")) |sub_arg| { |
| 5058 | try forks.append(arena, .{ | |
| 5059 | .manifest_ast = undefined, | |
| 5060 | .manifest = undefined, | |
| 5061 | .error_bundle = undefined, | |
| 5062 | .arena_allocator = undefined, | |
| 5063 | .path = .{ | |
| 5064 | .root_dir = .cwd(), | |
| 5065 | .sub_path = sub_arg, | |
| 5066 | }, | |
| 5067 | .failed = false, | |
| 5068 | }); | |
| 5100 | try forks.append(arena, .init(sub_arg)); | |
| 5069 | 5101 | continue; |
| 5070 | } else if (mem.eql(u8, arg, "--system")) { | |
| 5071 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); | |
| 5102 | } else if (mem.eql(u8, arg, "--fork")) { | |
| 5103 | if (i + 1 >= args.len) fatal("expected argument after: {s}", .{arg}); | |
| 5072 | 5104 | i += 1; |
| 5073 | system_pkg_dir_path = args[i]; | |
| 5074 | try child_argv.append(arena, "--system"); | |
| 5105 | try forks.append(arena, .init(args[i])); | |
| 5075 | 5106 | continue; |
| 5076 | 5107 | } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| { |
| 5077 | 5108 | reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| { |
| 5078 | fatal("unable to parse reference_trace count '{s}': {s}", .{ num, @errorName(err) }); | |
| 5109 | fatal("unable to parse reference_trace count '{s}': {t}", .{ num, err }); | |
| 5079 | 5110 | }; |
| 5080 | 5111 | } else if (mem.eql(u8, arg, "-fno-reference-trace")) { |
| 5081 | 5112 | reference_trace = null; |
| 5113 | } else if (mem.cutPrefix(u8, arg, "--maker-opt=")) |rest| { | |
| 5114 | maker_optimize_mode = parseOptimizeMode(rest); | |
| 5115 | continue; | |
| 5082 | 5116 | } else if (mem.eql(u8, arg, "--debug-log")) { |
| 5083 | 5117 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 5084 | try child_argv.appendSlice(arena, args[i .. i + 2]); | |
| 5118 | try make_argv.appendSlice(arena, args[i .. i + 2]); | |
| 5085 | 5119 | i += 1; |
| 5086 | 5120 | try addDebugLog(arena, args[i]); |
| 5087 | 5121 | continue; |
| ... | ... | @@ -5125,505 +5159,720 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, |
| 5125 | 5159 | verbose_llvm_bc = rest; |
| 5126 | 5160 | } else if (mem.eql(u8, arg, "--verbose-llvm-cpu-features")) { |
| 5127 | 5161 | verbose_llvm_cpu_features = true; |
| 5128 | } else if (mem.eql(u8, arg, "--color")) { | |
| 5129 | if (i + 1 >= args.len) fatal("expected [auto|on|off] after {s}", .{arg}); | |
| 5130 | i += 1; | |
| 5131 | color = std.meta.stringToEnum(Color, args[i]) orelse { | |
| 5132 | fatal("expected [auto|on|off] after {s}, found '{s}'", .{ arg, args[i] }); | |
| 5133 | }; | |
| 5134 | try child_argv.appendSlice(arena, &.{ arg, args[i] }); | |
| 5135 | continue; | |
| 5136 | 5162 | } else if (mem.cutPrefix(u8, arg, "-j")) |str| { |
| 5137 | const num = std.fmt.parseUnsigned(u32, str, 10) catch |err| { | |
| 5138 | fatal("unable to parse jobs count '{s}': {s}", .{ | |
| 5139 | str, @errorName(err), | |
| 5140 | }); | |
| 5141 | }; | |
| 5163 | const num = std.fmt.parseUnsigned(u32, str, 10) catch |err| | |
| 5164 | fatal("unable to parse jobs count {s}: {t}", .{ str, err }); | |
| 5142 | 5165 | if (num < 1) { |
| 5143 | fatal("number of jobs must be at least 1\n", .{}); | |
| 5166 | fatal("number of jobs must be at least 1", .{}); | |
| 5144 | 5167 | } |
| 5145 | 5168 | n_jobs = num; |
| 5146 | 5169 | } else if (mem.eql(u8, arg, "--seed")) { |
| 5147 | 5170 | if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); |
| 5148 | 5171 | i += 1; |
| 5149 | child_argv.items[argv_index_seed] = args[i]; | |
| 5172 | make_argv.items[argv_index_seed] = args[i]; | |
| 5150 | 5173 | continue; |
| 5151 | 5174 | } else if (mem.eql(u8, arg, "--")) { |
| 5152 | // The rest of the args are supposed to get passed onto | |
| 5153 | // build runner's `build.args` | |
| 5154 | try child_argv.appendSlice(arena, args[i..]); | |
| 5175 | try make_argv.appendSlice(arena, args[i..]); | |
| 5155 | 5176 | break; |
| 5156 | 5177 | } |
| 5157 | 5178 | } |
| 5158 | try child_argv.append(arena, arg); | |
| 5179 | try make_argv.append(arena, arg); | |
| 5159 | 5180 | } |
| 5160 | 5181 | } |
| 5161 | 5182 | |
| 5162 | 5183 | const root_prog_node = std.Progress.start(io, .{ |
| 5163 | 5184 | .disable_printing = (color == .off), |
| 5164 | .root_name = "Compile Build Script", | |
| 5185 | .root_name = "", | |
| 5165 | 5186 | }); |
| 5166 | 5187 | defer root_prog_node.end(); |
| 5167 | 5188 | |
| 5168 | // Normally the build runner is compiled for the host target but here is | |
| 5169 | // some code to help when debugging edits to the build runner so that you | |
| 5170 | // can make sure it compiles successfully on other targets. | |
| 5171 | const resolved_target: Package.Module.ResolvedTarget = t: { | |
| 5172 | if (build_options.enable_debug_extensions) { | |
| 5173 | if (debug_target) |triple| { | |
| 5174 | const target_query = try std.Target.Query.parse(.{ | |
| 5175 | .arch_os_abi = triple, | |
| 5176 | }); | |
| 5177 | break :t .{ | |
| 5178 | .result = std.zig.resolveTargetQueryOrFatal(io, target_query), | |
| 5179 | .is_native_os = false, | |
| 5180 | .is_native_abi = false, | |
| 5181 | .is_explicit_dynamic_linker = false, | |
| 5182 | }; | |
| 5183 | } | |
| 5184 | } | |
| 5185 | break :t .{ | |
| 5186 | .result = std.zig.resolveTargetQueryOrFatal(io, .{}), | |
| 5187 | .is_native_os = true, | |
| 5188 | .is_native_abi = true, | |
| 5189 | .is_explicit_dynamic_linker = false, | |
| 5190 | }; | |
| 5191 | }; | |
| 5192 | // Likewise, `--debug-libc` allows overriding the libc installation. | |
| 5193 | const libc_installation: ?*const LibCInstallation = lci: { | |
| 5194 | const paths_file = debug_libc_paths_file orelse break :lci null; | |
| 5195 | if (!build_options.enable_debug_extensions) unreachable; | |
| 5196 | const lci = try arena.create(LibCInstallation); | |
| 5197 | lci.* = try .parse(arena, io, paths_file, &resolved_target.result); | |
| 5198 | break :lci lci; | |
| 5199 | }; | |
| 5200 | ||
| 5201 | 5189 | process.raiseFileDescriptorLimit(); |
| 5202 | 5190 | |
| 5203 | const cwd_path = try introspect.getResolvedCwd(io, arena); | |
| 5191 | const cwd_path = introspect.getResolvedCwd(io, arena) catch |err| | |
| 5192 | fatal("failed to get current directory path: {t}", .{err}); | |
| 5193 | ||
| 5204 | 5194 | const build_root = try findBuildRoot(arena, io, .{ |
| 5205 | 5195 | .cwd_path = cwd_path, |
| 5206 | 5196 | .build_file = build_file, |
| 5207 | 5197 | }); |
| 5208 | 5198 | |
| 5209 | // This `init` calls `fatal` on error. | |
| 5210 | var dirs: Compilation.Directories = .init( | |
| 5211 | arena, | |
| 5212 | io, | |
| 5213 | override_lib_dir, | |
| 5214 | override_global_cache_dir, | |
| 5215 | .{ .override = path: { | |
| 5216 | if (override_local_cache_dir) |d| break :path d; | |
| 5217 | break :path try build_root.directory.join(arena, &.{introspect.default_local_zig_cache_basename}); | |
| 5218 | } }, | |
| 5219 | .empty, | |
| 5220 | self_exe_path, | |
| 5221 | environ_map, | |
| 5222 | ); | |
| 5223 | defer dirs.deinit(io); | |
| 5199 | { | |
| 5200 | // This `init` calls `fatal` on error. | |
| 5201 | var dirs: Compilation.Directories = .init( | |
| 5202 | arena, | |
| 5203 | io, | |
| 5204 | override_lib_dir, | |
| 5205 | override_global_cache_dir, | |
| 5206 | .{ .override = path: { | |
| 5207 | if (override_local_cache_dir) |d| break :path d; | |
| 5208 | break :path try build_root.directory.join(arena, &.{introspect.default_local_zig_cache_basename}); | |
| 5209 | } }, | |
| 5210 | .empty, | |
| 5211 | self_exe_path, | |
| 5212 | environ_map, | |
| 5213 | cwd_path, | |
| 5214 | ); | |
| 5215 | defer dirs.deinit(io); | |
| 5224 | 5216 | |
| 5225 | child_argv.items[argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path; | |
| 5226 | child_argv.items[argv_index_build_file] = build_root.directory.path orelse cwd_path; | |
| 5227 | child_argv.items[argv_index_global_cache_dir] = dirs.global_cache.path orelse cwd_path; | |
| 5228 | child_argv.items[argv_index_cache_dir] = dirs.local_cache.path orelse cwd_path; | |
| 5217 | const thread_limit = @min( | |
| 5218 | @max(n_jobs orelse std.Thread.getCpuCount() catch 1, 1), | |
| 5219 | std.math.maxInt(Zcu.PerThread.IdBacking), | |
| 5220 | ); | |
| 5221 | try setThreadLimit(arena, thread_limit); | |
| 5222 | ||
| 5223 | // Cache lookup for configure options. If we get a match, we can skip | |
| 5224 | // execution of the configure script. If not, we get the file path to pass | |
| 5225 | // to the configure process. | |
| 5226 | var local_cache: Cache = .{ | |
| 5227 | .gpa = gpa, | |
| 5228 | .io = io, | |
| 5229 | .manifest_dir = try dirs.local_cache.handle.createDirPathOpen(io, "h", .{}), | |
| 5230 | .cwd = cwd_path, | |
| 5231 | }; | |
| 5232 | local_cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() }); | |
| 5233 | local_cache.addPrefix(dirs.zig_lib); | |
| 5234 | local_cache.addPrefix(dirs.local_cache); | |
| 5235 | local_cache.addPrefix(dirs.global_cache); | |
| 5236 | defer local_cache.manifest_dir.close(io); | |
| 5237 | ||
| 5238 | var config_man = local_cache.obtain(); | |
| 5239 | defer config_man.deinit(); | |
| 5240 | config_man.hash.addBytes(build_options.version); | |
| 5241 | ||
| 5242 | for (cached_passthru_configure.items) |i| | |
| 5243 | config_man.hash.addBytes(configure_argv.items[i]); | |
| 5244 | ||
| 5245 | // Prevents a `zig build` from getting a false positive cache hit following | |
| 5246 | // a `zig build --cache-poison=ignored`. | |
| 5247 | config_man.hash.add(cache_poison == .ignored); | |
| 5248 | ||
| 5249 | // Normally the build runner is compiled for the host target but here is | |
| 5250 | // some code to help when debugging edits to the build runner so that you | |
| 5251 | // can make sure it compiles successfully on other targets. | |
| 5252 | const resolved_target: Package.Module.ResolvedTarget = t: { | |
| 5253 | if (build_options.enable_debug_extensions) { | |
| 5254 | if (debug_target) |triple| { | |
| 5255 | const target_query = try std.Target.Query.parse(.{ | |
| 5256 | .arch_os_abi = triple, | |
| 5257 | }); | |
| 5258 | config_man.hash.addBytes(triple); | |
| 5259 | break :t .{ | |
| 5260 | .result = std.zig.resolveTargetQueryOrFatal(io, target_query), | |
| 5261 | .is_native_os = false, | |
| 5262 | .is_native_abi = false, | |
| 5263 | .is_explicit_dynamic_linker = false, | |
| 5264 | }; | |
| 5265 | } | |
| 5266 | } | |
| 5267 | break :t .{ | |
| 5268 | .result = std.zig.resolveTargetQueryOrFatal(io, .{}), | |
| 5269 | .is_native_os = true, | |
| 5270 | .is_native_abi = true, | |
| 5271 | .is_explicit_dynamic_linker = false, | |
| 5272 | }; | |
| 5273 | }; | |
| 5229 | 5274 | |
| 5230 | const thread_limit = @min( | |
| 5231 | @max(n_jobs orelse std.Thread.getCpuCount() catch 1, 1), | |
| 5232 | std.math.maxInt(Zcu.PerThread.IdBacking), | |
| 5233 | ); | |
| 5234 | try setThreadLimit(arena, thread_limit); | |
| 5275 | // Likewise, `--debug-libc` allows overriding the libc installation. | |
| 5276 | const libc_installation: ?*const LibCInstallation = lci: { | |
| 5277 | const paths_file = debug_libc_paths_file orelse break :lci null; | |
| 5278 | if (!build_options.enable_debug_extensions) unreachable; | |
| 5279 | const lci = try arena.create(LibCInstallation); | |
| 5280 | lci.* = try .parse(arena, io, paths_file, &resolved_target.result); | |
| 5281 | LibCInstallation.addToHash(lci, &config_man.hash, resolved_target.result.abi); | |
| 5282 | break :lci lci; | |
| 5283 | }; | |
| 5235 | 5284 | |
| 5236 | // Dummy http client that is not actually used when fetch_command is unsupported. | |
| 5237 | // Prevents bootstrap from depending on a bunch of unnecessary stuff. | |
| 5238 | var http_client: if (dev.env.supports(.fetch_command)) std.http.Client else struct { | |
| 5239 | allocator: Allocator, | |
| 5240 | io: Io, | |
| 5241 | fn deinit(_: @This()) void {} | |
| 5242 | } = .{ .allocator = gpa, .io = io }; | |
| 5243 | defer http_client.deinit(); | |
| 5285 | // Kick off an optimized compilation of the make runner. | |
| 5286 | var make_runner_task = io.async(compileMakeRunner, .{ gpa, arena, io, .{ | |
| 5287 | .dirs = .{ | |
| 5288 | .cwd = dirs.cwd, | |
| 5289 | .zig_lib = dirs.zig_lib, | |
| 5290 | .global_cache = dirs.global_cache, | |
| 5291 | .local_cache = dirs.global_cache, | |
| 5292 | }, | |
| 5293 | .environ_map = environ_map, | |
| 5294 | .parent_prog_node = root_prog_node, | |
| 5295 | .resolved_target = resolved_target, | |
| 5296 | .libc_installation = libc_installation, | |
| 5297 | .thread_limit = thread_limit, | |
| 5298 | .self_exe_path = self_exe_path, | |
| 5299 | .color = color, | |
| 5300 | .reference_trace = reference_trace, | |
| 5301 | .optimize_mode = maker_optimize_mode, | |
| 5302 | } }); | |
| 5303 | defer _ = make_runner_task.cancel(io) catch {}; | |
| 5304 | ||
| 5305 | const pkg_root: Path = if (override_pkg_dir) |p| | |
| 5306 | .initCwd(p) | |
| 5307 | else if (system_pkg_dir_path) |p| | |
| 5308 | .initCwd(p) | |
| 5309 | else | |
| 5310 | .{ | |
| 5311 | .root_dir = build_root.directory, | |
| 5312 | .sub_path = "zig-pkg", | |
| 5313 | }; | |
| 5244 | 5314 | |
| 5245 | var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{}; | |
| 5246 | var fork_set: Package.Fetch.JobQueue.ForkSet = .{}; | |
| 5315 | make_argv.items[make_argv_index_zig_lib_dir] = dirs.zig_lib.path orelse cwd_path; | |
| 5316 | make_argv.items[make_argv_index_build_root] = build_root.directory.path orelse cwd_path; | |
| 5317 | make_argv.items[make_argv_index_global_cache_dir] = dirs.global_cache.path orelse cwd_path; | |
| 5318 | make_argv.items[make_argv_index_cache_dir] = dirs.local_cache.path orelse cwd_path; | |
| 5247 | 5319 | |
| 5248 | { | |
| 5249 | // Populate fork_set. | |
| 5250 | var group: Io.Group = .init; | |
| 5251 | defer group.cancel(io); | |
| 5252 | ||
| 5253 | for (forks.items) |*fork| | |
| 5254 | group.async(io, Fork.load, .{ io, gpa, fork, color }); | |
| 5255 | ||
| 5256 | try group.await(io); | |
| 5257 | ||
| 5258 | for (forks.items) |*fork| { | |
| 5259 | if (fork.failed) process.exit(1); | |
| 5260 | try fork_set.put(arena, .{ | |
| 5261 | .path = fork.path, | |
| 5262 | .manifest_ast = fork.manifest_ast, | |
| 5263 | .manifest = fork.manifest, | |
| 5264 | .uses = 0, | |
| 5265 | }, {}); | |
| 5266 | } | |
| 5267 | } | |
| 5268 | defer Fork.deinitList(forks.items); | |
| 5320 | configure_argv.items[conf_argv_index_build_root] = build_root.directory.path orelse cwd_path; | |
| 5321 | ||
| 5322 | // Dummy http client that is not actually used when fetch_command is unsupported. | |
| 5323 | // Prevents bootstrap from depending on a bunch of unnecessary stuff. | |
| 5324 | var http_client: if (dev.env.supports(.fetch_command)) std.http.Client else struct { | |
| 5325 | allocator: Allocator, | |
| 5326 | io: Io, | |
| 5327 | fn deinit(_: @This()) void {} | |
| 5328 | } = .{ .allocator = gpa, .io = io }; | |
| 5329 | defer http_client.deinit(); | |
| 5330 | ||
| 5331 | var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{}; | |
| 5332 | var fork_set: Package.Fetch.JobQueue.ForkSet = .{}; | |
| 5269 | 5333 | |
| 5270 | // This loop is re-evaluated when the build script exits with an indication that it | |
| 5271 | // could not continue due to missing lazy dependencies. | |
| 5272 | while (true) { | |
| 5273 | // We want to release all the locks before executing the child process, so we make a nice | |
| 5274 | // big block here to ensure the cleanup gets run when we extract out our argv. | |
| 5275 | 5334 | { |
| 5276 | const main_mod_paths: Package.Module.CreateOptions.Paths = if (override_build_runner) |runner| .{ | |
| 5277 | .root = try .fromUnresolved(arena, dirs, &.{fs.path.dirname(runner) orelse "."}), | |
| 5278 | .root_src_path = fs.path.basename(runner), | |
| 5279 | } else .{ | |
| 5280 | .root = try .fromRoot(arena, dirs, .zig_lib, "compiler"), | |
| 5281 | .root_src_path = "build_runner.zig", | |
| 5282 | }; | |
| 5335 | // Populate fork_set. | |
| 5336 | var group: Io.Group = .init; | |
| 5337 | defer group.cancel(io); | |
| 5338 | ||
| 5339 | for (forks.items) |*fork| | |
| 5340 | group.async(io, Fork.load, .{ io, gpa, fork, color }); | |
| 5341 | ||
| 5342 | try group.await(io); | |
| 5343 | ||
| 5344 | for (forks.items) |*fork| { | |
| 5345 | if (fork.failed) process.exit(1); | |
| 5346 | try fork_set.put(arena, .{ | |
| 5347 | .path = fork.path, | |
| 5348 | .manifest_ast = fork.manifest_ast, | |
| 5349 | .manifest = fork.manifest, | |
| 5350 | .uses = 0, | |
| 5351 | }, {}); | |
| 5352 | } | |
| 5353 | } | |
| 5354 | defer Fork.deinitList(forks.items); | |
| 5283 | 5355 | |
| 5284 | const config = try Compilation.Config.resolve(.{ | |
| 5285 | .output_mode = .Exe, | |
| 5286 | .resolved_target = resolved_target, | |
| 5287 | .have_zcu = true, | |
| 5288 | .emit_bin = true, | |
| 5289 | .is_test = false, | |
| 5290 | }); | |
| 5356 | // This loop is re-evaluated when the build script exits with an indication that it | |
| 5357 | // could not continue due to missing lazy dependencies. | |
| 5358 | const configuration_path: Path, const poisoned: bool = cp: while (true) { | |
| 5359 | // We want to release all the locks before executing the child process, so we make a nice | |
| 5360 | // big block here to ensure the cleanup gets run when we extract out our argv. | |
| 5361 | { | |
| 5362 | const main_mod_paths: Package.Module.CreateOptions.Paths = .{ | |
| 5363 | .root = try .fromRoot(arena, dirs, .zig_lib, "compiler"), | |
| 5364 | .root_src_path = "configurer.zig", | |
| 5365 | }; | |
| 5291 | 5366 | |
| 5292 | const root_mod = try Package.Module.create(arena, .{ | |
| 5293 | .paths = main_mod_paths, | |
| 5294 | .fully_qualified_name = "root", | |
| 5295 | .cc_argv = &.{}, | |
| 5296 | .inherited = .{ | |
| 5367 | const config = try Compilation.Config.resolve(.{ | |
| 5368 | .output_mode = .Exe, | |
| 5297 | 5369 | .resolved_target = resolved_target, |
| 5298 | }, | |
| 5299 | .global = config, | |
| 5300 | .parent = null, | |
| 5301 | }); | |
| 5370 | .have_zcu = true, | |
| 5371 | .emit_bin = true, | |
| 5372 | .is_test = false, | |
| 5373 | }); | |
| 5302 | 5374 | |
| 5303 | const build_mod = try Package.Module.create(arena, .{ | |
| 5304 | .paths = .{ | |
| 5305 | .root = try .fromUnresolved(arena, dirs, &.{build_root.directory.path orelse "."}), | |
| 5306 | .root_src_path = build_root.build_zig_basename, | |
| 5307 | }, | |
| 5308 | .fully_qualified_name = "root.@build", | |
| 5309 | .cc_argv = &.{}, | |
| 5310 | .inherited = .{}, | |
| 5311 | .global = config, | |
| 5312 | .parent = root_mod, | |
| 5313 | }); | |
| 5375 | const root_mod = try Package.Module.create(arena, .{ | |
| 5376 | .paths = main_mod_paths, | |
| 5377 | .fully_qualified_name = "root", | |
| 5378 | .cc_argv = &.{}, | |
| 5379 | .inherited = .{ | |
| 5380 | .resolved_target = resolved_target, | |
| 5381 | .single_threaded = true, | |
| 5382 | }, | |
| 5383 | .global = config, | |
| 5384 | .parent = null, | |
| 5385 | }); | |
| 5314 | 5386 | |
| 5315 | if (dev.env.supports(.fetch_command)) { | |
| 5316 | const fetch_prog_node = root_prog_node.start("Fetch Packages", 0); | |
| 5317 | defer fetch_prog_node.end(); | |
| 5318 | ||
| 5319 | // Reset fork match counts. | |
| 5320 | for (fork_set.keys()) |*fork| fork.uses = 0; | |
| 5321 | ||
| 5322 | var job_queue: Package.Fetch.JobQueue = .{ | |
| 5323 | .io = io, | |
| 5324 | .http_client = &http_client, | |
| 5325 | .global_cache = dirs.global_cache, | |
| 5326 | .local_storage = &.{ | |
| 5327 | .cache_root = .{ .root_dir = dirs.local_cache, .sub_path = "" }, | |
| 5328 | .pkg_root = if (override_pkg_dir) |p| | |
| 5329 | .initCwd(p) | |
| 5330 | else if (system_pkg_dir_path) |p| | |
| 5331 | .initCwd(p) | |
| 5332 | else | |
| 5333 | .{ | |
| 5334 | .root_dir = build_root.directory, | |
| 5335 | .sub_path = "zig-pkg", | |
| 5336 | }, | |
| 5387 | const build_mod = try Package.Module.create(arena, .{ | |
| 5388 | .paths = .{ | |
| 5389 | .root = try .fromUnresolved(arena, dirs, &.{build_root.directory.path orelse "."}), | |
| 5390 | .root_src_path = build_root.build_zig_basename, | |
| 5337 | 5391 | }, |
| 5338 | .recursive = true, | |
| 5339 | .debug_hash = false, | |
| 5340 | .unlazy_set = unlazy_set, | |
| 5341 | .fork_set = fork_set, | |
| 5342 | .mode = fetch_mode, | |
| 5343 | .prog_node = fetch_prog_node, | |
| 5344 | .read_only = system_pkg_dir_path != null, | |
| 5345 | }; | |
| 5346 | defer job_queue.deinit(); | |
| 5392 | .fully_qualified_name = "root.@build", | |
| 5393 | .cc_argv = &.{}, | |
| 5394 | .inherited = .{}, | |
| 5395 | .global = config, | |
| 5396 | .parent = root_mod, | |
| 5397 | }); | |
| 5347 | 5398 | |
| 5348 | if (system_pkg_dir_path == null) { | |
| 5349 | try http_client.initDefaultProxies(arena, environ_map); | |
| 5350 | } | |
| 5399 | if (dev.env.supports(.fetch_command)) { | |
| 5400 | const fetch_prog_node = root_prog_node.start("Fetch Packages", 0); | |
| 5401 | defer fetch_prog_node.end(); | |
| 5351 | 5402 | |
| 5352 | try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1); | |
| 5353 | try job_queue.table.ensureUnusedCapacity(gpa, 1); | |
| 5354 | ||
| 5355 | const phantom_package_root: Cache.Path = .{ .root_dir = build_root.directory }; | |
| 5356 | ||
| 5357 | var fetch: Package.Fetch = .{ | |
| 5358 | .arena = std.heap.ArenaAllocator.init(gpa), | |
| 5359 | .location = .{ .relative_path = phantom_package_root }, | |
| 5360 | .location_tok = 0, | |
| 5361 | .hash_tok = .none, | |
| 5362 | .name_tok = 0, | |
| 5363 | .lazy_status = .eager, | |
| 5364 | .remote_package_root = phantom_package_root, | |
| 5365 | .parent_package_root = phantom_package_root, | |
| 5366 | .parent_manifest_ast = null, | |
| 5367 | .prog_node = fetch_prog_node, | |
| 5368 | .job_queue = &job_queue, | |
| 5369 | .omit_missing_hash_error = true, | |
| 5370 | .allow_missing_paths_field = false, | |
| 5371 | .use_latest_commit = false, | |
| 5372 | ||
| 5373 | .package_root = undefined, | |
| 5374 | .error_bundle = undefined, | |
| 5375 | .manifest = undefined, | |
| 5376 | .manifest_ast = undefined, | |
| 5377 | .have_manifest = false, | |
| 5378 | .computed_hash = undefined, | |
| 5379 | .has_build_zig = true, | |
| 5380 | .oom_flag = false, | |
| 5381 | .latest_commit = null, | |
| 5382 | ||
| 5383 | .module = build_mod, | |
| 5384 | }; | |
| 5403 | // Reset fork match counts. | |
| 5404 | for (fork_set.keys()) |*fork| fork.uses = 0; | |
| 5405 | ||
| 5406 | var job_queue: Package.Fetch.JobQueue = .{ | |
| 5407 | .io = io, | |
| 5408 | .http_client = &http_client, | |
| 5409 | .global_cache = dirs.global_cache, | |
| 5410 | .local_storage = &.{ | |
| 5411 | .cache_root = .{ .root_dir = dirs.local_cache, .sub_path = "" }, | |
| 5412 | .pkg_root = pkg_root, | |
| 5413 | }, | |
| 5414 | .recursive = true, | |
| 5415 | .debug_hash = false, | |
| 5416 | .unlazy_set = unlazy_set, | |
| 5417 | .fork_set = fork_set, | |
| 5418 | .mode = fetch_mode, | |
| 5419 | .prog_node = fetch_prog_node, | |
| 5420 | .read_only = system_pkg_dir_path != null, | |
| 5421 | }; | |
| 5422 | defer job_queue.deinit(); | |
| 5385 | 5423 | |
| 5386 | job_queue.all_fetches.appendAssumeCapacity(&fetch); | |
| 5424 | if (system_pkg_dir_path == null) { | |
| 5425 | try http_client.initDefaultProxies(arena, environ_map); | |
| 5426 | } | |
| 5387 | 5427 | |
| 5388 | job_queue.table.putAssumeCapacityNoClobber( | |
| 5389 | Package.Fetch.relativePathDigest(phantom_package_root, dirs.global_cache), | |
| 5390 | &fetch, | |
| 5391 | ); | |
| 5428 | try job_queue.all_fetches.ensureUnusedCapacity(gpa, 1); | |
| 5429 | try job_queue.table.ensureUnusedCapacity(gpa, 1); | |
| 5430 | ||
| 5431 | const phantom_package_root: Cache.Path = .{ .root_dir = build_root.directory }; | |
| 5432 | ||
| 5433 | var fetch: Package.Fetch = .{ | |
| 5434 | .arena = std.heap.ArenaAllocator.init(gpa), | |
| 5435 | .location = .{ .relative_path = phantom_package_root }, | |
| 5436 | .location_tok = 0, | |
| 5437 | .hash_tok = .none, | |
| 5438 | .name_tok = 0, | |
| 5439 | .lazy_status = .eager, | |
| 5440 | .remote_package_root = phantom_package_root, | |
| 5441 | .parent_package_root = phantom_package_root, | |
| 5442 | .parent_manifest_ast = null, | |
| 5443 | .prog_node = fetch_prog_node, | |
| 5444 | .job_queue = &job_queue, | |
| 5445 | .omit_missing_hash_error = true, | |
| 5446 | .allow_missing_paths_field = false, | |
| 5447 | .use_latest_commit = false, | |
| 5448 | ||
| 5449 | .package_root = undefined, | |
| 5450 | .error_bundle = undefined, | |
| 5451 | .manifest = undefined, | |
| 5452 | .manifest_ast = undefined, | |
| 5453 | .have_manifest = false, | |
| 5454 | .computed_hash = undefined, | |
| 5455 | .has_build_zig = true, | |
| 5456 | .oom_flag = false, | |
| 5457 | .latest_commit = null, | |
| 5392 | 5458 | |
| 5393 | job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" }); | |
| 5394 | try job_queue.group.await(io); | |
| 5459 | .module = build_mod, | |
| 5460 | }; | |
| 5395 | 5461 | |
| 5396 | { | |
| 5397 | // Ensure that forks were actually used. This is done | |
| 5398 | // before printing manifest errors because using a fork can | |
| 5399 | // prevent them. | |
| 5400 | var any_unused = false; | |
| 5401 | for (fork_set.keys()) |*fork| { | |
| 5402 | if (fork.uses == 0) { | |
| 5403 | std.log.err("fork {f} matched no {s} packages", .{ | |
| 5404 | fork.path, fork.manifest.name, | |
| 5405 | }); | |
| 5406 | any_unused = true; | |
| 5407 | } else { | |
| 5408 | std.log.info("fork {f} matched {d} {s} packages", .{ | |
| 5409 | fork.path, fork.uses, fork.manifest.name, | |
| 5410 | }); | |
| 5462 | job_queue.all_fetches.appendAssumeCapacity(&fetch); | |
| 5463 | ||
| 5464 | job_queue.table.putAssumeCapacityNoClobber( | |
| 5465 | Package.Fetch.relativePathDigest(phantom_package_root, dirs.global_cache), | |
| 5466 | &fetch, | |
| 5467 | ); | |
| 5468 | ||
| 5469 | job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" }); | |
| 5470 | try job_queue.group.await(io); | |
| 5471 | ||
| 5472 | { | |
| 5473 | // Ensure that forks were actually used. This is done | |
| 5474 | // before printing manifest errors because using a fork can | |
| 5475 | // prevent them. | |
| 5476 | var any_unused = false; | |
| 5477 | for (fork_set.keys()) |*fork| { | |
| 5478 | if (fork.uses == 0) { | |
| 5479 | std.log.err("fork {f} matched no {s} packages", .{ | |
| 5480 | fork.path, fork.manifest.name, | |
| 5481 | }); | |
| 5482 | any_unused = true; | |
| 5483 | } else { | |
| 5484 | std.log.info("fork {f} matched {d} {s} packages", .{ | |
| 5485 | fork.path, fork.uses, fork.manifest.name, | |
| 5486 | }); | |
| 5487 | } | |
| 5411 | 5488 | } |
| 5489 | if (any_unused) process.exit(1); | |
| 5412 | 5490 | } |
| 5413 | if (any_unused) process.exit(1); | |
| 5414 | } | |
| 5415 | 5491 | |
| 5416 | try job_queue.consolidateErrors(); | |
| 5492 | try job_queue.consolidateErrors(); | |
| 5417 | 5493 | |
| 5418 | if (fetch.error_bundle.root_list.items.len > 0) { | |
| 5419 | var errors = try fetch.error_bundle.toOwnedBundle(""); | |
| 5420 | errors.renderToStderr(io, .{}, color) catch {}; | |
| 5421 | process.exit(1); | |
| 5422 | } | |
| 5494 | if (fetch.error_bundle.root_list.items.len > 0) { | |
| 5495 | var errors = try fetch.error_bundle.toOwnedBundle(""); | |
| 5496 | errors.renderToStderr(io, .{}, color) catch {}; | |
| 5497 | process.exit(1); | |
| 5498 | } | |
| 5499 | ||
| 5500 | if (fetch_only) return cleanExit(io); | |
| 5501 | ||
| 5502 | var source_buf = std.array_list.Managed(u8).init(gpa); | |
| 5503 | defer source_buf.deinit(); | |
| 5504 | try job_queue.createDependenciesSource(&source_buf); | |
| 5505 | const deps_mod = try createDependenciesModule( | |
| 5506 | arena, | |
| 5507 | io, | |
| 5508 | source_buf.items, | |
| 5509 | root_mod, | |
| 5510 | dirs, | |
| 5511 | config, | |
| 5512 | ); | |
| 5423 | 5513 | |
| 5424 | if (fetch_only) return cleanExit(io); | |
| 5514 | { | |
| 5515 | // We need a Module for each package's build.zig. | |
| 5516 | const hashes = job_queue.table.keys(); | |
| 5517 | const fetches = job_queue.table.values(); | |
| 5518 | try deps_mod.deps.ensureUnusedCapacity(arena, @intCast(hashes.len)); | |
| 5519 | for (hashes, fetches) |*hash, f| { | |
| 5520 | if (f == &fetch) { | |
| 5521 | // The first one is a dummy package for the current project. | |
| 5522 | continue; | |
| 5523 | } | |
| 5524 | if (!f.has_build_zig) | |
| 5525 | continue; | |
| 5526 | const hash_slice = hash.toSlice(); | |
| 5527 | const mod_root_path = try f.package_root.toString(arena); | |
| 5528 | const m = try Package.Module.create(arena, .{ | |
| 5529 | .paths = .{ | |
| 5530 | .root = try .fromUnresolved(arena, dirs, &.{mod_root_path}), | |
| 5531 | .root_src_path = Package.build_zig_basename, | |
| 5532 | }, | |
| 5533 | .fully_qualified_name = try std.fmt.allocPrint( | |
| 5534 | arena, | |
| 5535 | "root.@dependencies.{s}", | |
| 5536 | .{hash_slice}, | |
| 5537 | ), | |
| 5538 | .cc_argv = &.{}, | |
| 5539 | .inherited = .{}, | |
| 5540 | .global = config, | |
| 5541 | .parent = root_mod, | |
| 5542 | }); | |
| 5543 | const hash_cloned = try arena.dupe(u8, hash_slice); | |
| 5544 | deps_mod.deps.putAssumeCapacityNoClobber(hash_cloned, m); | |
| 5545 | f.module = m; | |
| 5546 | } | |
| 5425 | 5547 | |
| 5426 | var source_buf = std.array_list.Managed(u8).init(gpa); | |
| 5427 | defer source_buf.deinit(); | |
| 5428 | try job_queue.createDependenciesSource(&source_buf); | |
| 5429 | const deps_mod = try createDependenciesModule( | |
| 5548 | // Each build.zig module needs access to each of its | |
| 5549 | // dependencies' build.zig modules by name. | |
| 5550 | for (fetches) |f| { | |
| 5551 | const mod = f.module orelse continue; | |
| 5552 | if (!f.have_manifest) continue; | |
| 5553 | const man = &f.manifest; | |
| 5554 | const dep_names = man.dependencies.keys(); | |
| 5555 | try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len)); | |
| 5556 | for (dep_names, man.dependencies.values()) |name, dep| { | |
| 5557 | const dep_digest = Package.Fetch.depDigest( | |
| 5558 | f.package_root, | |
| 5559 | dirs.global_cache, | |
| 5560 | dep, | |
| 5561 | ) orelse continue; | |
| 5562 | const dep_mod = job_queue.table.get(dep_digest).?.module orelse continue; | |
| 5563 | const name_cloned = try arena.dupe(u8, name); | |
| 5564 | mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod); | |
| 5565 | } | |
| 5566 | } | |
| 5567 | } | |
| 5568 | } else try createEmptyDependenciesModule( | |
| 5430 | 5569 | arena, |
| 5431 | 5570 | io, |
| 5432 | source_buf.items, | |
| 5433 | 5571 | root_mod, |
| 5434 | 5572 | dirs, |
| 5435 | 5573 | config, |
| 5436 | 5574 | ); |
| 5437 | 5575 | |
| 5438 | { | |
| 5439 | // We need a Module for each package's build.zig. | |
| 5440 | const hashes = job_queue.table.keys(); | |
| 5441 | const fetches = job_queue.table.values(); | |
| 5442 | try deps_mod.deps.ensureUnusedCapacity(arena, @intCast(hashes.len)); | |
| 5443 | for (hashes, fetches) |*hash, f| { | |
| 5444 | if (f == &fetch) { | |
| 5445 | // The first one is a dummy package for the current project. | |
| 5446 | continue; | |
| 5447 | } | |
| 5448 | if (!f.has_build_zig) | |
| 5449 | continue; | |
| 5450 | const hash_slice = hash.toSlice(); | |
| 5451 | const mod_root_path = try f.package_root.toString(arena); | |
| 5452 | const m = try Package.Module.create(arena, .{ | |
| 5453 | .paths = .{ | |
| 5454 | .root = try .fromUnresolved(arena, dirs, &.{mod_root_path}), | |
| 5455 | .root_src_path = Package.build_zig_basename, | |
| 5456 | }, | |
| 5457 | .fully_qualified_name = try std.fmt.allocPrint( | |
| 5458 | arena, | |
| 5459 | "root.@dependencies.{s}", | |
| 5460 | .{hash_slice}, | |
| 5461 | ), | |
| 5462 | .cc_argv = &.{}, | |
| 5463 | .inherited = .{}, | |
| 5464 | .global = config, | |
| 5465 | .parent = root_mod, | |
| 5466 | }); | |
| 5467 | const hash_cloned = try arena.dupe(u8, hash_slice); | |
| 5468 | deps_mod.deps.putAssumeCapacityNoClobber(hash_cloned, m); | |
| 5469 | f.module = m; | |
| 5470 | } | |
| 5576 | const compile_prog_node = root_prog_node.start("Compile Configure Script", 0); | |
| 5577 | defer compile_prog_node.end(); | |
| 5578 | ||
| 5579 | try root_mod.deps.put(arena, "@build", build_mod); | |
| 5580 | ||
| 5581 | var create_diag: Compilation.CreateDiagnostic = undefined; | |
| 5582 | const comp = Compilation.create(gpa, arena, io, &create_diag, .{ | |
| 5583 | .libc_installation = libc_installation, | |
| 5584 | .dirs = dirs, | |
| 5585 | .root_name = "configure", | |
| 5586 | .config = config, | |
| 5587 | .root_mod = root_mod, | |
| 5588 | .main_mod = build_mod, | |
| 5589 | .emit_bin = .yes_cache, | |
| 5590 | .self_exe_path = self_exe_path, | |
| 5591 | .thread_limit = thread_limit, | |
| 5592 | .verbose_cc = verbose_cc, | |
| 5593 | .verbose_link = verbose_link, | |
| 5594 | .verbose_air = verbose_air, | |
| 5595 | .verbose_intern_pool = verbose_intern_pool, | |
| 5596 | .verbose_generic_instances = verbose_generic_instances, | |
| 5597 | .verbose_llvm_ir = verbose_llvm_ir, | |
| 5598 | .verbose_llvm_bc = verbose_llvm_bc, | |
| 5599 | .verbose_llvm_cpu_features = verbose_llvm_cpu_features, | |
| 5600 | .cache_mode = .whole, | |
| 5601 | .reference_trace = reference_trace, | |
| 5602 | .debug_compile_errors = debug_compile_errors, | |
| 5603 | .environ_map = environ_map, | |
| 5604 | }) catch |err| switch (err) { | |
| 5605 | error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), | |
| 5606 | else => |e| fatal("failed to create compilation: {t}", .{e}), | |
| 5607 | }; | |
| 5608 | defer comp.destroy(); | |
| 5471 | 5609 | |
| 5472 | // Each build.zig module needs access to each of its | |
| 5473 | // dependencies' build.zig modules by name. | |
| 5474 | for (fetches) |f| { | |
| 5475 | const mod = f.module orelse continue; | |
| 5476 | if (!f.have_manifest) continue; | |
| 5477 | const man = &f.manifest; | |
| 5478 | const dep_names = man.dependencies.keys(); | |
| 5479 | try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len)); | |
| 5480 | for (dep_names, man.dependencies.values()) |name, dep| { | |
| 5481 | const dep_digest = Package.Fetch.depDigest( | |
| 5482 | f.package_root, | |
| 5483 | dirs.global_cache, | |
| 5484 | dep, | |
| 5485 | ) orelse continue; | |
| 5486 | const dep_mod = job_queue.table.get(dep_digest).?.module orelse continue; | |
| 5487 | const name_cloned = try arena.dupe(u8, name); | |
| 5488 | mod.deps.putAssumeCapacityNoClobber(name_cloned, dep_mod); | |
| 5489 | } | |
| 5490 | } | |
| 5491 | } | |
| 5492 | } else try createEmptyDependenciesModule( | |
| 5493 | arena, | |
| 5494 | io, | |
| 5495 | root_mod, | |
| 5496 | dirs, | |
| 5497 | config, | |
| 5498 | ); | |
| 5610 | updateModule(comp, color, compile_prog_node) catch |err| switch (err) { | |
| 5611 | error.CompileErrorsReported => process.exit(2), | |
| 5612 | else => |e| return e, | |
| 5613 | }; | |
| 5499 | 5614 | |
| 5500 | try root_mod.deps.put(arena, "@build", build_mod); | |
| 5501 | ||
| 5502 | var create_diag: Compilation.CreateDiagnostic = undefined; | |
| 5503 | const comp = Compilation.create(gpa, arena, io, &create_diag, .{ | |
| 5504 | .libc_installation = libc_installation, | |
| 5505 | .dirs = dirs, | |
| 5506 | .root_name = "build", | |
| 5507 | .config = config, | |
| 5508 | .root_mod = root_mod, | |
| 5509 | .main_mod = build_mod, | |
| 5510 | .emit_bin = .yes_cache, | |
| 5511 | .self_exe_path = self_exe_path, | |
| 5512 | .thread_limit = thread_limit, | |
| 5513 | .verbose_cc = verbose_cc, | |
| 5514 | .verbose_link = verbose_link, | |
| 5515 | .verbose_air = verbose_air, | |
| 5516 | .verbose_intern_pool = verbose_intern_pool, | |
| 5517 | .verbose_generic_instances = verbose_generic_instances, | |
| 5518 | .verbose_llvm_ir = verbose_llvm_ir, | |
| 5519 | .verbose_llvm_bc = verbose_llvm_bc, | |
| 5520 | .verbose_llvm_cpu_features = verbose_llvm_cpu_features, | |
| 5521 | .cache_mode = .whole, | |
| 5522 | .reference_trace = reference_trace, | |
| 5523 | .debug_compile_errors = debug_compile_errors, | |
| 5524 | .environ_map = environ_map, | |
| 5525 | }) catch |err| switch (err) { | |
| 5526 | error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), | |
| 5527 | else => fatal("failed to create compilation: {t}", .{err}), | |
| 5528 | }; | |
| 5529 | defer comp.destroy(); | |
| 5615 | // Since incremental compilation isn't done yet, we use cache_mode = whole | |
| 5616 | // above, and thus the output file is already closed. | |
| 5617 | //try comp.makeBinFileExecutable(); | |
| 5618 | const hex_digest: []const u8 = &Cache.binToHex(comp.digest.?); | |
| 5619 | const exe_path: Path = .{ | |
| 5620 | .root_dir = dirs.local_cache, | |
| 5621 | .sub_path = try std.fmt.allocPrint(arena, "o/{s}/{s}", .{ hex_digest, comp.emit_bin.? }), | |
| 5622 | }; | |
| 5623 | _ = try config_man.addFilePath(exe_path, null); | |
| 5624 | configure_argv.items[0] = try exe_path.toString(arena); | |
| 5530 | 5625 | |
| 5531 | updateModule(comp, color, root_prog_node) catch |err| switch (err) { | |
| 5532 | error.CompileErrorsReported => process.exit(2), | |
| 5533 | else => |e| return e, | |
| 5534 | }; | |
| 5626 | switch (cache_poison) { | |
| 5627 | .pure, .disallowed, .ignored => if (try config_man.hit()) { | |
| 5628 | const digest = config_man.final(); | |
| 5629 | break :cp .{ | |
| 5630 | .{ | |
| 5631 | .root_dir = dirs.local_cache, | |
| 5632 | .sub_path = try std.fmt.allocPrint(arena, "c/{s}", .{&digest}), | |
| 5633 | }, | |
| 5634 | false, | |
| 5635 | }; | |
| 5636 | }, | |
| 5637 | .poisoned => {}, // Don't bother checking for cache hit. | |
| 5638 | } | |
| 5639 | } | |
| 5535 | 5640 | |
| 5536 | // Since incremental compilation isn't done yet, we use cache_mode = whole | |
| 5537 | // above, and thus the output file is already closed. | |
| 5538 | //try comp.makeBinFileExecutable(); | |
| 5539 | child_argv.items[argv_index_exe] = try dirs.local_cache.join(arena, &.{ | |
| 5540 | "o", | |
| 5541 | &Cache.binToHex(comp.digest.?), | |
| 5542 | comp.emit_bin.?, | |
| 5543 | }); | |
| 5544 | } | |
| 5641 | if (!process.can_spawn) { | |
| 5642 | const cmd = try std.mem.join(arena, " ", configure_argv.items); | |
| 5643 | fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ native_os, cmd }); | |
| 5644 | } | |
| 5545 | 5645 | |
| 5546 | if (!process.can_spawn) { | |
| 5547 | const cmd = try std.mem.join(arena, " ", child_argv.items); | |
| 5548 | fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ native_os, cmd }); | |
| 5549 | } | |
| 5550 | switch (term: { | |
| 5551 | _ = try io.lockStderr(&.{}, .no_color); | |
| 5552 | defer io.unlockStderr(); | |
| 5553 | var child = std.process.spawn(io, .{ | |
| 5554 | .argv = child_argv.items, | |
| 5555 | }) catch |err| fatal("failed to spawn build runner {s}: {t}", .{ child_argv.items[0], err }); | |
| 5556 | defer child.kill(io); | |
| 5557 | break :term child.wait(io) catch |err| | |
| 5558 | fatal("failed to wait build runner {s}: {t}", .{ child_argv.items[0], err }); | |
| 5559 | }) { | |
| 5560 | .exited => |code| { | |
| 5561 | if (code == 0) return cleanExit(io); | |
| 5562 | // Indicates that the build runner has reported compile errors | |
| 5563 | // and this parent process does not need to report any further | |
| 5564 | // diagnostics. | |
| 5565 | if (code == 2) process.exit(2); | |
| 5566 | ||
| 5567 | if (code == 3) { | |
| 5568 | if (!dev.env.supports(.fetch_command)) process.exit(3); | |
| 5569 | // Indicates the configure phase failed due to missing lazy | |
| 5570 | // dependencies and stdout contains the hashes of the ones | |
| 5571 | // that are missing. | |
| 5572 | const s = fs.path.sep_str; | |
| 5573 | const tmp_sub_path = "tmp" ++ s ++ results_tmp_file_nonce; | |
| 5574 | const stdout = dirs.local_cache.handle.readFileAlloc(io, tmp_sub_path, arena, .limited(50 * 1024 * 1024)) catch |err| { | |
| 5575 | fatal("unable to read results of configure phase from '{f}{s}': {t}", .{ | |
| 5576 | dirs.local_cache, tmp_sub_path, err, | |
| 5577 | }); | |
| 5578 | }; | |
| 5579 | dirs.local_cache.handle.deleteFile(io, tmp_sub_path) catch {}; | |
| 5580 | ||
| 5581 | var it = mem.splitScalar(u8, stdout, '\n'); | |
| 5582 | var any_errors = false; | |
| 5583 | while (it.next()) |hash| { | |
| 5584 | if (hash.len == 0) continue; | |
| 5585 | if (hash.len > Package.Hash.max_len) { | |
| 5586 | std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{ | |
| 5587 | hash.len, hash, | |
| 5588 | }); | |
| 5589 | any_errors = true; | |
| 5590 | continue; | |
| 5591 | } | |
| 5592 | try unlazy_set.put(arena, .fromSlice(hash), {}); | |
| 5646 | const rand_int = randInt(io, u64); | |
| 5647 | const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); | |
| 5648 | const config_tmp_path: Path = .{ | |
| 5649 | .root_dir = dirs.local_cache, | |
| 5650 | .sub_path = tmp_dir_sub_path, | |
| 5651 | }; | |
| 5652 | const config_tmp_file: Io.File = try config_tmp_path.root_dir.handle.createFile( | |
| 5653 | io, | |
| 5654 | config_tmp_path.sub_path, | |
| 5655 | .{ .read = true, .exclusive = true }, | |
| 5656 | ); | |
| 5657 | defer config_tmp_file.close(io); | |
| 5658 | ||
| 5659 | const term = term: { | |
| 5660 | const child_node = root_prog_node.start("Run Configure Script", 0); | |
| 5661 | defer child_node.end(); | |
| 5662 | var child = std.process.spawn(io, .{ | |
| 5663 | .argv = configure_argv.items, | |
| 5664 | .stdout = .{ .file = config_tmp_file }, | |
| 5665 | .progress_node = child_node, | |
| 5666 | }) catch |err| fatal("failed to spawn configure script {s}: {t}", .{ configure_argv.items[0], err }); | |
| 5667 | defer child.kill(io); | |
| 5668 | break :term child.wait(io) catch |err| | |
| 5669 | fatal("failed to wait configure script {s}: {t}", .{ configure_argv.items[0], err }); | |
| 5670 | }; | |
| 5671 | if (!term.success()) { | |
| 5672 | // Failure to produce the configuration file. | |
| 5673 | const cmd = try std.mem.join(arena, " ", configure_argv.items); | |
| 5674 | fatal("the following configure command {f}:\n{s}", .{ term, cmd }); | |
| 5675 | } | |
| 5676 | // Even though the file is designed to be sent directly to make | |
| 5677 | // runner, we must load it now because: | |
| 5678 | // * If it contains additional file dependencies, we need to | |
| 5679 | // add them to `config_man` before obtaining the final digest. | |
| 5680 | // * If it contains a set of lazy packages that need to be | |
| 5681 | // fetched, we need to fetch those now and re-run configure. | |
| 5682 | var configuration = std.Build.Configuration.loadFile(arena, io, config_tmp_file) catch |err| | |
| 5683 | fatal("failed to load configuration file {f}: {t}", .{ config_tmp_path, err }); | |
| 5684 | ||
| 5685 | if (configuration.unlazy_deps.len != 0) { | |
| 5686 | if (!dev.env.supports(.fetch_command)) process.exit(1); | |
| 5687 | var any_errors = false; | |
| 5688 | for (configuration.unlazy_deps) |hash_string| { | |
| 5689 | const hash = hash_string.slice(&configuration); | |
| 5690 | assert(hash.len != 0); | |
| 5691 | if (hash.len > Package.Hash.max_len) { | |
| 5692 | std.log.err("invalid digest (length {d} exceeds maximum): '{s}'", .{ hash.len, hash }); | |
| 5693 | any_errors = true; | |
| 5694 | continue; | |
| 5593 | 5695 | } |
| 5594 | if (any_errors) process.exit(3); | |
| 5595 | if (system_pkg_dir_path) |p| { | |
| 5596 | // In this mode, the system needs to provide these packages; they | |
| 5597 | // cannot be fetched by Zig. | |
| 5598 | for (unlazy_set.keys()) |*hash| { | |
| 5599 | std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ | |
| 5600 | p, hash.toSlice(), | |
| 5601 | }); | |
| 5602 | } | |
| 5603 | std.log.info("remote package fetching disabled due to --system mode", .{}); | |
| 5604 | std.log.info("dependencies might be avoidable depending on build configuration", .{}); | |
| 5605 | process.exit(3); | |
| 5696 | try unlazy_set.put(arena, .fromSlice(hash), {}); | |
| 5697 | } | |
| 5698 | if (any_errors) process.exit(1); | |
| 5699 | if (system_pkg_dir_path) |p| { | |
| 5700 | // In this mode, the system needs to provide these packages; they | |
| 5701 | // cannot be fetched by Zig. | |
| 5702 | const s = fs.path.sep_str; | |
| 5703 | for (unlazy_set.keys()) |*hash| { | |
| 5704 | std.log.err("lazy dependency package not found: {s}" ++ s ++ "{s}", .{ p, hash.toSlice() }); | |
| 5606 | 5705 | } |
| 5607 | continue; | |
| 5706 | std.log.info("remote package fetching disabled due to --system mode", .{}); | |
| 5707 | std.log.info("dependencies might be avoidable depending on build configuration", .{}); | |
| 5708 | process.exit(1); | |
| 5608 | 5709 | } |
| 5710 | continue :cp; | |
| 5711 | } | |
| 5609 | 5712 | |
| 5610 | const cmd = try std.mem.join(arena, " ", child_argv.items); | |
| 5611 | fatal("the following build command failed with exit code {d}:\n{s}", .{ code, cmd }); | |
| 5612 | }, | |
| 5613 | .signal => |sig| { | |
| 5614 | const cmd = try std.mem.join(arena, " ", child_argv.items); | |
| 5615 | fatal("the following build command terminated with signal {t}:\n{s}", .{ sig, cmd }); | |
| 5616 | }, | |
| 5617 | .stopped => |sig| { | |
| 5618 | const cmd = try std.mem.join(arena, " ", child_argv.items); | |
| 5619 | fatal("the following build command stopped with signal {t}:\n{s}", .{ sig, cmd }); | |
| 5620 | }, | |
| 5621 | .unknown => { | |
| 5622 | const cmd = try std.mem.join(arena, " ", child_argv.items); | |
| 5623 | fatal("the following build command crashed:\n{s}", .{cmd}); | |
| 5624 | }, | |
| 5713 | for (configuration.path_deps_base, configuration.path_deps_sub) |base, sub| { | |
| 5714 | const conf_path: std.Build.Configuration.Path = .{ .base = base, .sub = sub }; | |
| 5715 | try config_man.addPathPost(conf_path.toCachePath(&configuration, arena)); | |
| 5716 | } | |
| 5717 | ||
| 5718 | // If it is poisoned, there is no point in moving it to cached | |
| 5719 | // location. Just leave it in the tmp directory. | |
| 5720 | if (configuration.poisoned) { | |
| 5721 | break :cp .{ config_tmp_path, true }; | |
| 5722 | } else { | |
| 5723 | const digest = config_man.final(); | |
| 5724 | const final_path: Path = .{ | |
| 5725 | .root_dir = dirs.local_cache, | |
| 5726 | .sub_path = try std.fmt.allocPrint(arena, "c/{s}", .{&digest}), | |
| 5727 | }; | |
| 5728 | Io.Dir.rename( | |
| 5729 | config_tmp_path.root_dir.handle, | |
| 5730 | config_tmp_path.sub_path, | |
| 5731 | final_path.root_dir.handle, | |
| 5732 | final_path.sub_path, | |
| 5733 | io, | |
| 5734 | ) catch |err| retry: { | |
| 5735 | const e = switch (err) { | |
| 5736 | error.FileNotFound => e: { | |
| 5737 | const dir_path = final_path.dirname().?; | |
| 5738 | dir_path.root_dir.handle.createDirPath(io, dir_path.sub_path) catch |e| | |
| 5739 | fatal("failed to create directory {f}: {t}", .{ dir_path, e }); | |
| 5740 | if (Io.Dir.rename( | |
| 5741 | config_tmp_path.root_dir.handle, | |
| 5742 | config_tmp_path.sub_path, | |
| 5743 | final_path.root_dir.handle, | |
| 5744 | final_path.sub_path, | |
| 5745 | io, | |
| 5746 | )) |_| break :retry else |e| break :e e; | |
| 5747 | }, | |
| 5748 | else => |e| e, | |
| 5749 | }; | |
| 5750 | fatal("failed to rename configuration file from {f} into {f}: {t}", .{ | |
| 5751 | config_tmp_path, final_path, e, | |
| 5752 | }); | |
| 5753 | }; | |
| 5754 | config_man.writeManifest() catch |err| warn("failed to write cache manifest: {t}", .{err}); | |
| 5755 | break :cp .{ final_path, false }; | |
| 5756 | } | |
| 5757 | }; | |
| 5758 | ||
| 5759 | { | |
| 5760 | // Release all file system locks just before running the maker process. | |
| 5761 | var configuration_lock = if (!poisoned) config_man.toOwnedLock() else null; | |
| 5762 | defer if (configuration_lock) |*l| l.release(io); | |
| 5763 | ||
| 5764 | const make_runner = make_runner_task.await(io) catch |err| fatal("failed compiling maker: {t}", .{err}); | |
| 5765 | ||
| 5766 | make_argv.items[0] = try make_runner.exe_path.toString(arena); | |
| 5767 | make_argv.items[argv_index_configuration_file] = try configuration_path.toString(arena); | |
| 5625 | 5768 | } |
| 5626 | 5769 | } |
| 5770 | ||
| 5771 | if (!process.can_spawn) { | |
| 5772 | const cmd = try std.mem.join(arena, " ", make_argv.items); | |
| 5773 | fatal("the following command cannot be executed ({t} does not support spawning a child process):\n{s}", .{ | |
| 5774 | native_os, cmd, | |
| 5775 | }); | |
| 5776 | } | |
| 5777 | ||
| 5778 | const term = term: { | |
| 5779 | _ = try io.lockStderr(&.{}, .no_color); | |
| 5780 | defer io.unlockStderr(); | |
| 5781 | var child = std.process.spawn(io, .{ | |
| 5782 | .argv = make_argv.items, | |
| 5783 | }) catch |err| fatal("failed spawning maker {s}: {t}", .{ make_argv.items[0], err }); | |
| 5784 | defer child.kill(io); | |
| 5785 | break :term child.wait(io) catch |err| | |
| 5786 | fatal("failed waiting on maker {s}: {t}", .{ make_argv.items[0], err }); | |
| 5787 | }; | |
| 5788 | if (term.success()) return cleanExit(io); | |
| 5789 | const cmd = try std.mem.join(arena, " ", make_argv.items); | |
| 5790 | fatal("the following maker command {f}:\n{s}", .{ term, cmd }); | |
| 5791 | } | |
| 5792 | ||
| 5793 | const MakeRunner = struct { | |
| 5794 | exe_path: Path, | |
| 5795 | ||
| 5796 | const Options = struct { | |
| 5797 | environ_map: *const process.Environ.Map, | |
| 5798 | dirs: Compilation.Directories, | |
| 5799 | parent_prog_node: std.Progress.Node, | |
| 5800 | resolved_target: Package.Module.ResolvedTarget, | |
| 5801 | libc_installation: ?*const LibCInstallation, | |
| 5802 | self_exe_path: []const u8, | |
| 5803 | thread_limit: usize, | |
| 5804 | color: Color, | |
| 5805 | reference_trace: ?u32, | |
| 5806 | optimize_mode: std.builtin.OptimizeMode, | |
| 5807 | }; | |
| 5808 | }; | |
| 5809 | ||
| 5810 | fn compileMakeRunner(gpa: Allocator, arena: Allocator, io: Io, options: MakeRunner.Options) !MakeRunner { | |
| 5811 | const compile_prog_node = options.parent_prog_node.start("Compile Maker", 0); | |
| 5812 | defer compile_prog_node.end(); | |
| 5813 | ||
| 5814 | const strip = options.optimize_mode != .Debug; | |
| 5815 | ||
| 5816 | const main_mod_paths: Package.Module.CreateOptions.Paths = .{ | |
| 5817 | .root = try .fromRoot(arena, options.dirs, .zig_lib, "compiler"), | |
| 5818 | .root_src_path = "Maker.zig", | |
| 5819 | }; | |
| 5820 | ||
| 5821 | const config = try Compilation.Config.resolve(.{ | |
| 5822 | .output_mode = .Exe, | |
| 5823 | .root_strip = strip, | |
| 5824 | .root_optimize_mode = options.optimize_mode, | |
| 5825 | .resolved_target = options.resolved_target, | |
| 5826 | .have_zcu = true, | |
| 5827 | .emit_bin = true, | |
| 5828 | .is_test = false, | |
| 5829 | }); | |
| 5830 | ||
| 5831 | const root_mod = try Package.Module.create(arena, .{ | |
| 5832 | .paths = main_mod_paths, | |
| 5833 | .fully_qualified_name = "root", | |
| 5834 | .cc_argv = &.{}, | |
| 5835 | .inherited = .{ | |
| 5836 | .resolved_target = options.resolved_target, | |
| 5837 | .optimize_mode = options.optimize_mode, | |
| 5838 | .strip = strip, | |
| 5839 | }, | |
| 5840 | .global = config, | |
| 5841 | .parent = null, | |
| 5842 | }); | |
| 5843 | ||
| 5844 | var create_diag: Compilation.CreateDiagnostic = undefined; | |
| 5845 | const comp = Compilation.create(gpa, arena, io, &create_diag, .{ | |
| 5846 | .dirs = options.dirs, | |
| 5847 | .root_name = "maker", | |
| 5848 | .config = config, | |
| 5849 | .root_mod = root_mod, | |
| 5850 | .main_mod = root_mod, | |
| 5851 | .emit_bin = .yes_cache, | |
| 5852 | .self_exe_path = options.self_exe_path, | |
| 5853 | .thread_limit = options.thread_limit, | |
| 5854 | .cache_mode = .whole, | |
| 5855 | .environ_map = options.environ_map, | |
| 5856 | .reference_trace = options.reference_trace, | |
| 5857 | }) catch |err| switch (err) { | |
| 5858 | error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), | |
| 5859 | error.Canceled => |e| return e, | |
| 5860 | else => |e| fatal("failed to create compilation: {t}", .{e}), | |
| 5861 | }; | |
| 5862 | defer comp.destroy(); | |
| 5863 | ||
| 5864 | try updateModule(comp, options.color, compile_prog_node); | |
| 5865 | ||
| 5866 | const exe_path: Path = .{ | |
| 5867 | .root_dir = options.dirs.global_cache, | |
| 5868 | .sub_path = try std.fmt.allocPrint(arena, "o/{s}/{s}", .{ | |
| 5869 | &Cache.binToHex(comp.digest.?), comp.emit_bin.?, | |
| 5870 | }), | |
| 5871 | }; | |
| 5872 | ||
| 5873 | return .{ | |
| 5874 | .exe_path = exe_path, | |
| 5875 | }; | |
| 5627 | 5876 | } |
| 5628 | 5877 | |
| 5629 | 5878 | const Fork = struct { |
| ... | ... | @@ -5634,6 +5883,20 @@ const Fork = struct { |
| 5634 | 5883 | failed: bool, |
| 5635 | 5884 | arena_allocator: std.heap.ArenaAllocator, |
| 5636 | 5885 | |
| 5886 | fn init(cwd_relative_path: []const u8) Fork { | |
| 5887 | return .{ | |
| 5888 | .manifest_ast = undefined, | |
| 5889 | .manifest = undefined, | |
| 5890 | .error_bundle = undefined, | |
| 5891 | .arena_allocator = undefined, | |
| 5892 | .path = .{ | |
| 5893 | .root_dir = .cwd(), | |
| 5894 | .sub_path = cwd_relative_path, | |
| 5895 | }, | |
| 5896 | .failed = false, | |
| 5897 | }; | |
| 5898 | } | |
| 5899 | ||
| 5637 | 5900 | fn load(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void { |
| 5638 | 5901 | loadFallible(io, gpa, fork, color) catch |err| switch (err) { |
| 5639 | 5902 | error.Canceled => |e| return e, |
| ... | ... | @@ -5749,6 +6012,8 @@ fn jitCmdInner( |
| 5749 | 6012 | const override_lib_dir: ?[]const u8 = EnvVar.ZIG_LIB_DIR.get(environ_map); |
| 5750 | 6013 | const override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); |
| 5751 | 6014 | |
| 6015 | const cwd_path = try introspect.getResolvedCwd(io, arena); | |
| 6016 | ||
| 5752 | 6017 | // This `init` calls `fatal` on error. |
| 5753 | 6018 | var dirs: Compilation.Directories = .init( |
| 5754 | 6019 | arena, |
| ... | ... | @@ -5759,6 +6024,7 @@ fn jitCmdInner( |
| 5759 | 6024 | preopens, |
| 5760 | 6025 | self_exe_path, |
| 5761 | 6026 | environ_map, |
| 6027 | cwd_path, | |
| 5762 | 6028 | ); |
| 5763 | 6029 | defer dirs.deinit(io); |
| 5764 | 6030 | |
| ... | ... | @@ -5829,7 +6095,7 @@ fn jitCmdInner( |
| 5829 | 6095 | .environ_map = environ_map, |
| 5830 | 6096 | }) catch |err| switch (err) { |
| 5831 | 6097 | error.CreateFail => fatal("failed to create compilation: {f}", .{create_diag}), |
| 5832 | else => fatal("failed to create compilation: {s}", .{@errorName(err)}), | |
| 6098 | else => fatal("failed to create compilation: {t}", .{err}), | |
| 5833 | 6099 | }; |
| 5834 | 6100 | defer comp.destroy(); |
| 5835 | 6101 | |
| ... | ... | @@ -6582,7 +6848,11 @@ fn warnAboutForeignBinaries( |
| 6582 | 6848 | const host_query: std.Target.Query = .{}; |
| 6583 | 6849 | const host_target = std.zig.resolveTargetQueryOrFatal(io, host_query); |
| 6584 | 6850 | |
| 6585 | switch (std.zig.system.getExternalExecutor(io, &host_target, target, .{ .link_libc = link_libc })) { | |
| 6851 | switch (std.zig.system.getExternalExecutor(io, target, .{ | |
| 6852 | .host_cpu_arch = host_target.cpu.arch, | |
| 6853 | .host_os_tag = host_target.os.tag, | |
| 6854 | .link_libc = link_libc, | |
| 6855 | })) { | |
| 6586 | 6856 | .native => return, |
| 6587 | 6857 | .rosetta => { |
| 6588 | 6858 | const host_name = try host_target.zigTriple(arena); |
src/print_env.zig+4| ... | ... | @@ -8,6 +8,7 @@ const fatal = std.process.fatal; |
| 8 | 8 | |
| 9 | 9 | const build_options = @import("build_options"); |
| 10 | 10 | const Compilation = @import("Compilation.zig"); |
| 11 | const introspect = @import("introspect.zig"); | |
| 11 | 12 | |
| 12 | 13 | pub fn cmdEnv( |
| 13 | 14 | arena: Allocator, |
| ... | ... | @@ -28,6 +29,8 @@ pub fn cmdEnv( |
| 28 | 29 | }, |
| 29 | 30 | }; |
| 30 | 31 | |
| 32 | const cwd_path = try introspect.getResolvedCwd(io, arena); | |
| 33 | ||
| 31 | 34 | var dirs: Compilation.Directories = .init( |
| 32 | 35 | arena, |
| 33 | 36 | io, |
| ... | ... | @@ -37,6 +40,7 @@ pub fn cmdEnv( |
| 37 | 40 | preopens, |
| 38 | 41 | if (builtin.target.os.tag != .wasi) self_exe_path, |
| 39 | 42 | environ_map, |
| 43 | cwd_path, | |
| 40 | 44 | ); |
| 41 | 45 | defer dirs.deinit(io); |
| 42 | 46 |
test/src/Cases.zig+18-10| ... | ... | @@ -470,8 +470,9 @@ pub fn lowerToBuildSteps( |
| 470 | 470 | options: CaseTestOptions, |
| 471 | 471 | ) void { |
| 472 | 472 | const io = self.io; |
| 473 | const graph = b.graph; | |
| 474 | const arena = graph.arena; | |
| 473 | 475 | const host = b.resolveTargetQuery(.{}); |
| 474 | const cases_dir_path = b.build_root.join(b.allocator, &.{ "test", "cases" }) catch @panic("OOM"); | |
| 475 | 476 | |
| 476 | 477 | for (self.cases.items) |case| { |
| 477 | 478 | for (options.test_filters) |test_filter| { |
| ... | ... | @@ -504,7 +505,7 @@ pub fn lowerToBuildSteps( |
| 504 | 505 | ); |
| 505 | 506 | if (options.skip_llvm and would_use_llvm) continue; |
| 506 | 507 | |
| 507 | const triple_txt = case.target.query.zigTriple(b.allocator) catch @panic("OOM"); | |
| 508 | const triple_txt = case.target.query.zigTriple(arena) catch @panic("OOM"); | |
| 508 | 509 | |
| 509 | 510 | if (options.test_target_filters.len > 0) { |
| 510 | 511 | for (options.test_target_filters) |filter| { |
| ... | ... | @@ -516,7 +517,7 @@ pub fn lowerToBuildSteps( |
| 516 | 517 | continue; |
| 517 | 518 | |
| 518 | 519 | const writefiles = b.addWriteFiles(); |
| 519 | var file_sources = std.StringHashMap(std.Build.LazyPath).init(b.allocator); | |
| 520 | var file_sources = std.StringHashMap(std.Build.LazyPath).init(arena); | |
| 520 | 521 | defer file_sources.deinit(); |
| 521 | 522 | const first_file = case.files.items[0]; |
| 522 | 523 | const root_source_file = writefiles.add(first_file.path, first_file.src); |
| ... | ... | @@ -526,12 +527,15 @@ pub fn lowerToBuildSteps( |
| 526 | 527 | } |
| 527 | 528 | |
| 528 | 529 | for (case.imports) |import_rel| { |
| 529 | const import_abs = std.fs.path.join(b.allocator, &.{ | |
| 530 | cases_dir_path, | |
| 531 | case.import_path orelse @panic("import_path not set"), | |
| 532 | import_rel, | |
| 533 | }) catch @panic("OOM"); | |
| 534 | _ = writefiles.addCopyFile(.{ .cwd_relative = import_abs }, import_rel); | |
| 530 | _ = writefiles.addCopyFile(.{ .src_path = .{ | |
| 531 | .owner = b, | |
| 532 | .sub_path = b.pathJoin(&.{ | |
| 533 | "test", | |
| 534 | "cases", | |
| 535 | case.import_path orelse @panic("import_path not set"), | |
| 536 | import_rel, | |
| 537 | }), | |
| 538 | } }, import_rel); | |
| 535 | 539 | } |
| 536 | 540 | |
| 537 | 541 | const mod = b.createModule(.{ |
| ... | ... | @@ -605,7 +609,11 @@ pub fn lowerToBuildSteps( |
| 605 | 609 | }, |
| 606 | 610 | .Execution => |expected_stdout| no_exec: { |
| 607 | 611 | const run = if (case.target.result.ofmt == .c) run_step: { |
| 608 | if (getExternalExecutor(io, &host.result, &case.target.result, .{ .link_libc = true }) != .native) { | |
| 612 | if (getExternalExecutor(io, &case.target.result, .{ | |
| 613 | .host_cpu_arch = host.result.cpu.arch, | |
| 614 | .host_os_tag = host.result.os.tag, | |
| 615 | .link_libc = true, | |
| 616 | }) != .native) { | |
| 609 | 617 | // We wouldn't be able to run the compiled C code. |
| 610 | 618 | break :no_exec; |
| 611 | 619 | } |
test/src/Libc.zig+5-2| ... | ... | @@ -31,9 +31,11 @@ pub fn addLibcTestCase( |
| 31 | 31 | supports_wasi_libc: bool, |
| 32 | 32 | options: LibcTestCaseOption, |
| 33 | 33 | ) void { |
| 34 | const name = libc.b.dupe(path[0 .. path.len - std.fs.path.extension(path).len]); | |
| 34 | const graph = libc.b.graph; | |
| 35 | const arena = graph.arena; | |
| 36 | const name = arena.dupe(u8, path[0 .. path.len - std.fs.path.extension(path).len]) catch @panic("OOM"); | |
| 35 | 37 | std.mem.replaceScalar(u8, name, '/', '.'); |
| 36 | libc.test_cases.append(libc.b.allocator, .{ | |
| 38 | libc.test_cases.append(arena, .{ | |
| 37 | 39 | .name = name, |
| 38 | 40 | .src_file = libc.libc_test_src_path.path(libc.b, path), |
| 39 | 41 | .additional_src_file = if (options.additional_src_file) |additional_src_file| libc.libc_test_src_path.path(libc.b, additional_src_file) else null, |
| ... | ... | @@ -112,6 +114,7 @@ pub fn addTarget(libc: *const Libc, target: std.Build.ResolvedTarget) void { |
| 112 | 114 | const run = libc.b.addRunArtifact(exe); |
| 113 | 115 | run.setName(annotated_case_name); |
| 114 | 116 | run.skip_foreign_checks = true; |
| 117 | run.disable_zig_progress = true; // can interfere with fd count assumptions | |
| 115 | 118 | run.expectStdErrEqual(""); |
| 116 | 119 | run.expectStdOutEqual(""); |
| 117 | 120 | run.expectExitCode(0); |
test/standalone/build.zig.zon-6| ... | ... | @@ -153,9 +153,6 @@ |
| 153 | 153 | .compiler_rt_panic = .{ |
| 154 | 154 | .path = "compiler_rt_panic", |
| 155 | 155 | }, |
| 156 | .ios = .{ | |
| 157 | .path = "ios", | |
| 158 | }, | |
| 159 | 156 | .depend_on_main_mod = .{ |
| 160 | 157 | .path = "depend_on_main_mod", |
| 161 | 158 | }, |
| ... | ... | @@ -171,9 +168,6 @@ |
| 171 | 168 | .run_output_paths = .{ |
| 172 | 169 | .path = "run_output_paths", |
| 173 | 170 | }, |
| 174 | .run_output_caching = .{ | |
| 175 | .path = "run_output_caching", | |
| 176 | }, | |
| 177 | 171 | .empty_global_error_set = .{ |
| 178 | 172 | .path = "empty_global_error_set", |
| 179 | 173 | }, |
test/standalone/cmakedefine/build.zig-1| ... | ... | @@ -48,7 +48,6 @@ pub fn build(b: *std.Build) void { |
| 48 | 48 | .include_path = "stack.h", |
| 49 | 49 | }, |
| 50 | 50 | .{ |
| 51 | .AT = "@", | |
| 52 | 51 | .UNDERSCORE = "_", |
| 53 | 52 | .NEST_UNDERSCORE_PROXY = "UNDERSCORE", |
| 54 | 53 | .NEST_PROXY = "NEST_UNDERSCORE_PROXY", |
test/standalone/dependency_options/build.zig+1-1| ... | ... | @@ -10,7 +10,7 @@ pub fn build(b: *std.Build) !void { |
| 10 | 10 | |
| 11 | 11 | const none_specified_mod = none_specified.module("dummy"); |
| 12 | 12 | if (!none_specified_mod.resolved_target.?.query.eql(b.graph.host.query)) return error.TestFailed; |
| 13 | const expected_optimize: std.builtin.OptimizeMode = switch (b.release_mode) { | |
| 13 | const expected_optimize: std.builtin.OptimizeMode = switch (b.graph.release_mode) { | |
| 14 | 14 | .off => .Debug, |
| 15 | 15 | .any => unreachable, |
| 16 | 16 | .fast => .ReleaseFast, |
test/standalone/dirname/build.zig+6-29| ... | ... | @@ -27,41 +27,16 @@ pub fn build(b: *std.Build) void { |
| 27 | 27 | }), |
| 28 | 28 | }); |
| 29 | 29 | |
| 30 | const has_basename = b.addExecutable(.{ | |
| 31 | .name = "has_basename", | |
| 32 | .root_module = b.createModule(.{ | |
| 33 | .root_source_file = b.path("has_basename.zig"), | |
| 34 | .optimize = .Debug, | |
| 35 | .target = target, | |
| 36 | }), | |
| 37 | }); | |
| 38 | ||
| 39 | // Known path: | |
| 40 | addTestRun(test_step, exists_in, touch_src.dirname(), &.{"touch.zig"}); | |
| 41 | ||
| 42 | // Generated file: | |
| 43 | addTestRun(test_step, exists_in, generated.dirname(), &.{"generated.txt"}); | |
| 44 | ||
| 45 | // Generated file multiple levels: | |
| 46 | addTestRun(test_step, exists_in, generated.dirname().dirname(), &.{ | |
| 30 | addTestRun(test_step, exists_in, "run exists_in (known path)", touch_src.dirname(), &.{"touch.zig"}); | |
| 31 | addTestRun(test_step, exists_in, "run exists_in (generated file)", generated.dirname(), &.{"generated.txt"}); | |
| 32 | addTestRun(test_step, exists_in, "run exists_in (generated file multi level)", generated.dirname().dirname(), &.{ | |
| 47 | 33 | "subdir" ++ std.fs.path.sep_str ++ "generated.txt", |
| 48 | 34 | }); |
| 49 | 35 | |
| 50 | // Cache root: | |
| 51 | const cache_dir = b.cache_root.path orelse | |
| 52 | (b.cache_root.join(b.allocator, &.{"."}) catch @panic("OOM")); | |
| 53 | addTestRun( | |
| 54 | test_step, | |
| 55 | has_basename, | |
| 56 | generated.dirname().dirname().dirname().dirname(), | |
| 57 | &.{std.fs.path.basename(cache_dir)}, | |
| 58 | ); | |
| 59 | ||
| 60 | // Absolute path: | |
| 61 | 36 | const write_files = b.addWriteFiles(); |
| 62 | 37 | _ = write_files.add("foo.txt", ""); |
| 63 | 38 | const abs_path = write_files.getDirectory(); |
| 64 | addTestRun(test_step, exists_in, abs_path, &.{"foo.txt"}); | |
| 39 | addTestRun(test_step, exists_in, "run exists_in (absolute path)", abs_path, &.{"foo.txt"}); | |
| 65 | 40 | } |
| 66 | 41 | |
| 67 | 42 | // Runs exe with the parameters [dirname, args...]. |
| ... | ... | @@ -69,10 +44,12 @@ pub fn build(b: *std.Build) void { |
| 69 | 44 | fn addTestRun( |
| 70 | 45 | test_step: *std.Build.Step, |
| 71 | 46 | exe: *std.Build.Step.Compile, |
| 47 | step_name: []const u8, | |
| 72 | 48 | dirname: std.Build.LazyPath, |
| 73 | 49 | args: []const []const u8, |
| 74 | 50 | ) void { |
| 75 | 51 | const run = test_step.owner.addRunArtifact(exe); |
| 52 | run.setName(step_name); | |
| 76 | 53 | run.addDirectoryArg(dirname); |
| 77 | 54 | run.addArgs(args); |
| 78 | 55 | run.expectExitCode(0); |
test/standalone/dirname/touch.zig+9-10| ... | ... | @@ -7,27 +7,26 @@ |
| 7 | 7 | //! Path must be absolute. |
| 8 | 8 | |
| 9 | 9 | const std = @import("std"); |
| 10 | const Io = std.Io; | |
| 10 | 11 | |
| 11 | 12 | pub fn main(init: std.process.Init) !void { |
| 13 | const io = init.io; | |
| 14 | ||
| 12 | 15 | var args = try init.minimal.args.iterateAllocator(init.gpa); |
| 13 | 16 | defer args.deinit(); |
| 14 | _ = args.next() orelse unreachable; // skip binary name | |
| 17 | _ = args.next().?; // skip binary name | |
| 15 | 18 | |
| 16 | 19 | const path = args.next() orelse { |
| 17 | 20 | std.log.err("missing <path> argument", .{}); |
| 18 | 21 | return error.BadUsage; |
| 19 | 22 | }; |
| 20 | 23 | |
| 21 | const dir_path = std.Io.Dir.path.dirname(path) orelse unreachable; | |
| 22 | const basename = std.Io.Dir.path.basename(path); | |
| 23 | ||
| 24 | const io = std.Io.Threaded.global_single_threaded.io(); | |
| 24 | const dir_path = Io.Dir.path.dirname(path).?; | |
| 25 | const basename = Io.Dir.path.basename(path); | |
| 25 | 26 | |
| 26 | var dir = try std.Io.Dir.cwd().openDir(io, dir_path, .{}); | |
| 27 | var dir = try Io.Dir.cwd().openDir(io, dir_path, .{}); | |
| 27 | 28 | defer dir.close(io); |
| 28 | 29 | |
| 29 | _ = dir.statFile(io, basename, .{}) catch { | |
| 30 | var file = try dir.createFile(io, basename, .{}); | |
| 31 | file.close(io); | |
| 32 | }; | |
| 30 | var file = try dir.createFile(io, basename, .{ .truncate = false }); | |
| 31 | file.close(io); | |
| 33 | 32 | } |
test/standalone/install_headers/build.zig+1-1| ... | ... | @@ -106,7 +106,7 @@ pub fn build(b: *std.Build) void { |
| 106 | 106 | "custom/include/foo/config.h", |
| 107 | 107 | "custom/include/bar.h", |
| 108 | 108 | }); |
| 109 | run_check_exists.setCwd(.{ .cwd_relative = b.getInstallPath(.prefix, "") }); | |
| 109 | run_check_exists.setCwd(.{ .relative = .{ .base = .install_prefix } }); | |
| 110 | 110 | run_check_exists.expectExitCode(0); |
| 111 | 111 | run_check_exists.step.dependOn(&install_libfoo.step); |
| 112 | 112 | test_step.dependOn(&run_check_exists.step); |
test/standalone/ios/build.zig deleted-40| ... | ... | @@ -1,40 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | pub const requires_symlinks = true; | |
| 4 | pub const requires_ios_sdk = true; | |
| 5 | ||
| 6 | pub fn build(b: *std.Build) void { | |
| 7 | const test_step = b.step("test", "Test it"); | |
| 8 | b.default_step = test_step; | |
| 9 | ||
| 10 | const optimize: std.builtin.OptimizeMode = .Debug; | |
| 11 | const target = b.resolveTargetQuery(.{ | |
| 12 | .cpu_arch = .aarch64, | |
| 13 | .os_tag = .ios, | |
| 14 | }); | |
| 15 | ||
| 16 | const exe = b.addExecutable(.{ | |
| 17 | .name = "main", | |
| 18 | .root_module = b.createModule(.{ | |
| 19 | .root_source_file = null, | |
| 20 | .optimize = optimize, | |
| 21 | .target = target, | |
| 22 | .link_libc = true, | |
| 23 | }), | |
| 24 | }); | |
| 25 | ||
| 26 | const io = b.graph.io; | |
| 27 | ||
| 28 | if (std.zig.system.darwin.getSdk(b.allocator, io, &target.result)) |sdk| { | |
| 29 | b.sysroot = sdk; | |
| 30 | exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) }); | |
| 31 | exe.root_module.addSystemFrameworkPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/System/Library/Frameworks" }) }); | |
| 32 | exe.root_module.addLibraryPath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/lib" }) }); | |
| 33 | } else { | |
| 34 | exe.step.dependOn(&b.addFail("no iOS SDK found").step); | |
| 35 | } | |
| 36 | ||
| 37 | exe.root_module.addCSourceFile(.{ .file = b.path("main.m"), .flags = &.{} }); | |
| 38 | exe.root_module.linkFramework("Foundation", .{}); | |
| 39 | exe.root_module.linkFramework("UIKit", .{}); | |
| 40 | } |
test/standalone/ios/main.m deleted-34| ... | ... | @@ -1,34 +0,0 @@ |
| 1 | #import <UIKit/UIKit.h> | |
| 2 | ||
| 3 | @interface AppDelegate : UIResponder <UIApplicationDelegate> | |
| 4 | @property (strong, nonatomic) UIWindow *window; | |
| 5 | @end | |
| 6 | ||
| 7 | int main() { | |
| 8 | @autoreleasepool { | |
| 9 | return UIApplicationMain(0, nil, nil, NSStringFromClass([AppDelegate class])); | |
| 10 | } | |
| 11 | } | |
| 12 | ||
| 13 | @implementation AppDelegate | |
| 14 | ||
| 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(id)options { | |
| 16 | CGRect mainScreenBounds = [[UIScreen mainScreen] bounds]; | |
| 17 | self.window = [[UIWindow alloc] initWithFrame:mainScreenBounds]; | |
| 18 | UIViewController *viewController = [[UIViewController alloc] init]; | |
| 19 | viewController.view.frame = mainScreenBounds; | |
| 20 | ||
| 21 | NSString* msg = @"Hello world"; | |
| 22 | ||
| 23 | UILabel *label = [[UILabel alloc] initWithFrame:mainScreenBounds]; | |
| 24 | [label setText:msg]; | |
| 25 | [viewController.view addSubview: label]; | |
| 26 | ||
| 27 | self.window.rootViewController = viewController; | |
| 28 | ||
| 29 | [self.window makeKeyAndVisible]; | |
| 30 | ||
| 31 | return YES; | |
| 32 | } | |
| 33 | ||
| 34 | @end |
test/standalone/libfuzzer/build.zig+1-1| ... | ... | @@ -24,6 +24,6 @@ pub fn build(b: *std.Build) void { |
| 24 | 24 | b.default_step = run_step; |
| 25 | 25 | |
| 26 | 26 | const run_artifact = b.addRunArtifact(exe); |
| 27 | run_artifact.addArg(b.cache_root.path orelse ""); | |
| 27 | run_artifact.addFileArg(.cache_root); | |
| 28 | 28 | run_step.dependOn(&run_artifact.step); |
| 29 | 29 | } |
test/standalone/run_output_caching/build.zig deleted-140| ... | ... | @@ -1,140 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("std"); | |
| 3 | ||
| 4 | pub fn build(b: *std.Build) void { | |
| 5 | const test_step = b.step("test", "Test it"); | |
| 6 | b.default_step = test_step; | |
| 7 | ||
| 8 | if (builtin.os.tag == .windows) return; // https://codeberg.org/ziglang/zig/issues/31564 | |
| 9 | ||
| 10 | const target = b.standardTargetOptions(.{}); | |
| 11 | const optimize = b.standardOptimizeOption(.{}); | |
| 12 | ||
| 13 | const exe = b.addExecutable(.{ | |
| 14 | .name = "create-file", | |
| 15 | .root_module = b.createModule(.{ | |
| 16 | .root_source_file = b.path("main.zig"), | |
| 17 | .target = target, | |
| 18 | .optimize = optimize, | |
| 19 | }), | |
| 20 | }); | |
| 21 | ||
| 22 | { | |
| 23 | const run_random_with_sideeffects_first = b.addRunArtifact(exe); | |
| 24 | run_random_with_sideeffects_first.setName("run with side-effects (first)"); | |
| 25 | run_random_with_sideeffects_first.has_side_effects = true; | |
| 26 | ||
| 27 | const run_random_with_sideeffects_second = b.addRunArtifact(exe); | |
| 28 | run_random_with_sideeffects_second.setName("run with side-effects (second)"); | |
| 29 | run_random_with_sideeffects_second.has_side_effects = true; | |
| 30 | ||
| 31 | // ensure that "second" runs after "first" | |
| 32 | run_random_with_sideeffects_second.step.dependOn(&run_random_with_sideeffects_first.step); | |
| 33 | ||
| 34 | const first_output = run_random_with_sideeffects_first.addOutputFileArg("a.txt"); | |
| 35 | const second_output = run_random_with_sideeffects_second.addOutputFileArg("a.txt"); | |
| 36 | ||
| 37 | const expect_uncached_dependencies = CheckOutputCaching.init(b, false, &.{ first_output, second_output }); | |
| 38 | test_step.dependOn(&expect_uncached_dependencies.step); | |
| 39 | ||
| 40 | const expect_unequal_output = CheckPathEquality.init(b, true, &.{ first_output, second_output }); | |
| 41 | test_step.dependOn(&expect_unequal_output.step); | |
| 42 | ||
| 43 | const check_first_output = b.addCheckFile(first_output, .{ .expected_matches = &.{"a.txt"} }); | |
| 44 | test_step.dependOn(&check_first_output.step); | |
| 45 | const check_second_output = b.addCheckFile(second_output, .{ .expected_matches = &.{"a.txt"} }); | |
| 46 | test_step.dependOn(&check_second_output.step); | |
| 47 | } | |
| 48 | ||
| 49 | { | |
| 50 | const run_random_without_sideeffects_1 = b.addRunArtifact(exe); | |
| 51 | run_random_without_sideeffects_1.setName("run without side-effects (A)"); | |
| 52 | ||
| 53 | const run_random_without_sideeffects_2 = b.addRunArtifact(exe); | |
| 54 | run_random_without_sideeffects_2.setName("run without side-effects (B)"); | |
| 55 | ||
| 56 | run_random_without_sideeffects_2.step.dependOn(&run_random_without_sideeffects_1.step); | |
| 57 | ||
| 58 | const first_output = run_random_without_sideeffects_1.addOutputFileArg("a.txt"); | |
| 59 | const second_output = run_random_without_sideeffects_2.addOutputFileArg("a.txt"); | |
| 60 | ||
| 61 | const expect_cached_dependencies = CheckOutputCaching.init(b, true, &.{second_output}); | |
| 62 | test_step.dependOn(&expect_cached_dependencies.step); | |
| 63 | ||
| 64 | const expect_equal_output = CheckPathEquality.init(b, true, &.{ first_output, second_output }); | |
| 65 | test_step.dependOn(&expect_equal_output.step); | |
| 66 | ||
| 67 | const check_first_output = b.addCheckFile(first_output, .{ .expected_matches = &.{"a.txt"} }); | |
| 68 | test_step.dependOn(&check_first_output.step); | |
| 69 | const check_second_output = b.addCheckFile(second_output, .{ .expected_matches = &.{"a.txt"} }); | |
| 70 | test_step.dependOn(&check_second_output.step); | |
| 71 | } | |
| 72 | } | |
| 73 | ||
| 74 | const CheckOutputCaching = struct { | |
| 75 | step: std.Build.Step, | |
| 76 | expect_caching: bool, | |
| 77 | ||
| 78 | pub fn init(owner: *std.Build, expect_caching: bool, output_paths: []const std.Build.LazyPath) *CheckOutputCaching { | |
| 79 | const check = owner.allocator.create(CheckOutputCaching) catch @panic("OOM"); | |
| 80 | check.* = .{ | |
| 81 | .step = std.Build.Step.init(.{ | |
| 82 | .id = .custom, | |
| 83 | .name = "check output caching", | |
| 84 | .owner = owner, | |
| 85 | .makeFn = make, | |
| 86 | }), | |
| 87 | .expect_caching = expect_caching, | |
| 88 | }; | |
| 89 | for (output_paths) |output_path| { | |
| 90 | output_path.addStepDependencies(&check.step); | |
| 91 | } | |
| 92 | return check; | |
| 93 | } | |
| 94 | ||
| 95 | fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { | |
| 96 | const check: *CheckOutputCaching = @fieldParentPtr("step", step); | |
| 97 | ||
| 98 | for (step.dependencies.items) |dependency| { | |
| 99 | if (check.expect_caching) { | |
| 100 | if (dependency.result_cached) continue; | |
| 101 | return step.fail("expected '{s}' step to be cached, but it was not", .{dependency.name}); | |
| 102 | } else { | |
| 103 | if (!dependency.result_cached) continue; | |
| 104 | return step.fail("expected '{s}' step to not be cached, but it was", .{dependency.name}); | |
| 105 | } | |
| 106 | } | |
| 107 | } | |
| 108 | }; | |
| 109 | ||
| 110 | const CheckPathEquality = struct { | |
| 111 | step: std.Build.Step, | |
| 112 | expected_equality: bool, | |
| 113 | output_paths: []const std.Build.LazyPath, | |
| 114 | ||
| 115 | pub fn init(owner: *std.Build, expected_equality: bool, output_paths: []const std.Build.LazyPath) *CheckPathEquality { | |
| 116 | const check = owner.allocator.create(CheckPathEquality) catch @panic("OOM"); | |
| 117 | check.* = .{ | |
| 118 | .step = std.Build.Step.init(.{ | |
| 119 | .id = .custom, | |
| 120 | .name = "check output path equality", | |
| 121 | .owner = owner, | |
| 122 | .makeFn = make, | |
| 123 | }), | |
| 124 | .expected_equality = expected_equality, | |
| 125 | .output_paths = owner.allocator.dupe(std.Build.LazyPath, output_paths) catch @panic("OOM"), | |
| 126 | }; | |
| 127 | for (output_paths) |output_path| { | |
| 128 | output_path.addStepDependencies(&check.step); | |
| 129 | } | |
| 130 | return check; | |
| 131 | } | |
| 132 | ||
| 133 | fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { | |
| 134 | const check: *CheckPathEquality = @fieldParentPtr("step", step); | |
| 135 | std.debug.assert(check.output_paths.len != 0); | |
| 136 | for (check.output_paths[0 .. check.output_paths.len - 1], check.output_paths[1..]) |a, b| { | |
| 137 | try std.testing.expectEqual(check.expected_equality, std.mem.eql(u8, a.getPath(step.owner), b.getPath(step.owner))); | |
| 138 | } | |
| 139 | } | |
| 140 | }; |
test/standalone/run_output_caching/main.zig deleted-11| ... | ... | @@ -1,11 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | pub fn main(init: std.process.Init) !void { | |
| 4 | const io = init.io; | |
| 5 | var args = try init.minimal.args.iterateAllocator(init.arena.allocator()); | |
| 6 | _ = args.skip(); | |
| 7 | const filename = args.next().?; | |
| 8 | const file = try std.Io.Dir.cwd().createFile(io, filename, .{}); | |
| 9 | defer file.close(io); | |
| 10 | try file.writeStreamingAll(io, filename); | |
| 11 | } |
test/standalone/windows_resources/build.zig+1-1| ... | ... | @@ -38,7 +38,7 @@ fn add( |
| 38 | 38 | .file = b.path("res/zig.rc"), |
| 39 | 39 | .flags = &.{"/c65001"}, // UTF-8 code page |
| 40 | 40 | .include_paths = &.{ |
| 41 | .{ .generated = .{ .file = &generated_h_step.generated_directory } }, | |
| 41 | .{ .generated = .{ .index = generated_h_step.generated_directory } }, | |
| 42 | 42 | }, |
| 43 | 43 | }); |
| 44 | 44 | exe.rc_includes = switch (rc_includes) { |
test/tests.zig+10-7| ... | ... | @@ -2433,8 +2433,10 @@ pub fn addCliTests(b: *std.Build) *Step { |
| 2433 | 2433 | }); |
| 2434 | 2434 | run_test.addArg("--build-file"); |
| 2435 | 2435 | run_test.addFileArg(b.path("test/cli/options/build.zig")); |
| 2436 | ||
| 2436 | 2437 | run_test.addArg("--cache-dir"); |
| 2437 | run_test.addFileArg(.{ .cwd_relative = b.cache_root.join(b.allocator, &.{}) catch @panic("OOM") }); | |
| 2438 | run_test.addFileArg(.cache_root); | |
| 2439 | ||
| 2438 | 2440 | run_test.setName("test build options"); |
| 2439 | 2441 | |
| 2440 | 2442 | step.dependOn(&run_test.step); |
| ... | ... | @@ -2890,7 +2892,7 @@ pub fn addCases( |
| 2890 | 2892 | |
| 2891 | 2893 | var cases = @import("src/Cases.zig").init(gpa, arena, io); |
| 2892 | 2894 | |
| 2893 | var dir = try b.build_root.handle.openDir(io, "test/cases", .{ .iterate = true }); | |
| 2895 | var dir = try b.root.openDir(io, "test/cases", .{ .iterate = true }); | |
| 2894 | 2896 | defer dir.close(io); |
| 2895 | 2897 | |
| 2896 | 2898 | cases.addFromDir(dir, b); |
| ... | ... | @@ -2948,7 +2950,7 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons |
| 2948 | 2950 | }), |
| 2949 | 2951 | }); |
| 2950 | 2952 | |
| 2951 | var dir = try b.build_root.handle.openDir(io, "test/incremental", .{ .iterate = true }); | |
| 2953 | var dir = try b.root.openDir(io, "test/incremental", .{ .iterate = true }); | |
| 2952 | 2954 | defer dir.close(io); |
| 2953 | 2955 | |
| 2954 | 2956 | var it = try dir.walk(b.graph.arena); |
| ... | ... | @@ -2966,10 +2968,11 @@ pub fn addIncrementalTests(b: *std.Build, test_step: *Step, test_filters: []cons |
| 2966 | 2968 | |
| 2967 | 2969 | run.addArg(b.graph.zig_exe); |
| 2968 | 2970 | run.addFileArg(b.path("test/incremental/").path(b, entry.path)); |
| 2969 | run.addArgs(&.{ | |
| 2970 | "--zig-lib-dir", b.graph.zig_lib_directory.path orelse ".", | |
| 2971 | "--target", target_str, | |
| 2972 | }); | |
| 2971 | ||
| 2972 | run.addArg("--zig-lib-dir"); | |
| 2973 | run.addDirectoryArg(.zig_lib); | |
| 2974 | ||
| 2975 | run.addArgs(&.{ "--target", target_str }); | |
| 2973 | 2976 | |
| 2974 | 2977 | run.addArg("--quiet"); // don't fill stderr telling us about skipped tests etc |
| 2975 | 2978 |
tools/docgen.zig+16-5| ... | ... | @@ -3,6 +3,7 @@ const builtin = @import("builtin"); |
| 3 | 3 | const std = @import("std"); |
| 4 | 4 | const Io = std.Io; |
| 5 | 5 | const Dir = std.Io.Dir; |
| 6 | const Path = std.Build.Cache.Path; | |
| 6 | 7 | const process = std.process; |
| 7 | 8 | const Progress = std.Progress; |
| 8 | 9 | const print = std.debug.print; |
| ... | ... | @@ -73,8 +74,13 @@ pub fn main(init: std.process.Init) !void { |
| 73 | 74 | var out_file_buffer: [4096]u8 = undefined; |
| 74 | 75 | var out_file_writer = out_file.writer(io, &out_file_buffer); |
| 75 | 76 | |
| 76 | var code_dir = try Dir.cwd().openDir(io, code_dir_path, .{}); | |
| 77 | defer code_dir.close(io); | |
| 77 | var code_dir: Path = .{ | |
| 78 | .root_dir = .{ | |
| 79 | .handle = try Dir.cwd().openDir(io, code_dir_path, .{}), | |
| 80 | .path = code_dir_path, | |
| 81 | }, | |
| 82 | }; | |
| 83 | defer code_dir.root_dir.handle.close(io); | |
| 78 | 84 | |
| 79 | 85 | var in_file_reader = in_file.reader(io, &.{}); |
| 80 | 86 | const input_file_bytes = try in_file_reader.interface.allocRemaining(arena, .limited(max_doc_file_size)); |
| ... | ... | @@ -988,7 +994,7 @@ fn genHtml( |
| 988 | 994 | io: Io, |
| 989 | 995 | tokenizer: *Tokenizer, |
| 990 | 996 | toc: *Toc, |
| 991 | code_dir: Dir, | |
| 997 | code_dir: Path, | |
| 992 | 998 | out: *Writer, |
| 993 | 999 | ) !void { |
| 994 | 1000 | for (toc.nodes) |node| { |
| ... | ... | @@ -1044,8 +1050,13 @@ fn genHtml( |
| 1044 | 1050 | }); |
| 1045 | 1051 | defer allocator.free(out_basename); |
| 1046 | 1052 | |
| 1047 | const contents = code_dir.readFileAlloc(io, out_basename, allocator, .limited(std.math.maxInt(u32))) catch |err| { | |
| 1048 | return parseError(tokenizer, code.token, "unable to open '{s}': {t}", .{ out_basename, err }); | |
| 1053 | const out_path: Path = .{ | |
| 1054 | .root_dir = code_dir.root_dir, | |
| 1055 | .sub_path = out_basename, | |
| 1056 | }; | |
| 1057 | ||
| 1058 | const contents = out_path.root_dir.handle.readFileAlloc(io, out_path.sub_path, allocator, .unlimited) catch |err| { | |
| 1059 | return parseError(tokenizer, code.token, "failed opening {f}: {t}", .{ out_path, err }); | |
| 1049 | 1060 | }; |
| 1050 | 1061 | defer allocator.free(contents); |
| 1051 | 1062 |
tools/doctest.zig+7-2| ... | ... | @@ -311,7 +311,9 @@ fn printOutput( |
| 311 | 311 | .arch_os_abi = triple, |
| 312 | 312 | }); |
| 313 | 313 | const target = try std.zig.system.resolveTargetQuery(io, target_query); |
| 314 | switch (getExternalExecutor(io, &host, &target, .{ | |
| 314 | switch (getExternalExecutor(io, &target, .{ | |
| 315 | .host_cpu_arch = host.cpu.arch, | |
| 316 | .host_os_tag = host.os.tag, | |
| 315 | 317 | .link_libc = code.link_libc, |
| 316 | 318 | })) { |
| 317 | 319 | .native => {}, |
| ... | ... | @@ -526,7 +528,10 @@ fn printOutput( |
| 526 | 528 | .lib => { |
| 527 | 529 | const bin_basename = try std.zig.binNameAlloc(arena, .{ |
| 528 | 530 | .root_name = code_name, |
| 529 | .target = &builtin.target, | |
| 531 | .cpu_arch = builtin.target.cpu.arch, | |
| 532 | .os_tag = builtin.target.os.tag, | |
| 533 | .ofmt = builtin.target.ofmt, | |
| 534 | .abi = builtin.target.abi, | |
| 530 | 535 | .output_mode = .Lib, |
| 531 | 536 | }); |
| 532 | 537 |
tools/incr-check.zig+9-3| ... | ... | @@ -360,7 +360,10 @@ const Eval = struct { |
| 360 | 360 | |
| 361 | 361 | const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{ |
| 362 | 362 | .root_name = "root", // corresponds to the module name "root" |
| 363 | .target = &eval.target, | |
| 363 | .cpu_arch = eval.target.cpu.arch, | |
| 364 | .os_tag = eval.target.os.tag, | |
| 365 | .ofmt = eval.target.ofmt, | |
| 366 | .abi = eval.target.abi, | |
| 364 | 367 | .output_mode = .Exe, |
| 365 | 368 | }); |
| 366 | 369 | const bin_path = try Dir.path.join(arena, &.{ result_dir, bin_name }); |
| ... | ... | @@ -487,9 +490,12 @@ const Eval = struct { |
| 487 | 490 | var argv_buf: [2][]const u8 = undefined; |
| 488 | 491 | const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor( |
| 489 | 492 | io, |
| 490 | &eval.host, | |
| 491 | 493 | &eval.target, |
| 492 | .{ .link_libc = eval.backend == .cbe }, | |
| 494 | .{ | |
| 495 | .link_libc = eval.backend == .cbe, | |
| 496 | .host_cpu_arch = eval.host.cpu.arch, | |
| 497 | .host_os_tag = eval.host.os.tag, | |
| 498 | }, | |
| 493 | 499 | )) { |
| 494 | 500 | .bad_dl, .bad_os_or_cpu => { |
| 495 | 501 | // This binary cannot be executed on this host. |