Commit 5ce962eb69

mlugg <mlugg@mlugg.co.uk>
2024-09-26 04:58:39
incr-check: better progress output, support external executors
If no external executor is available for a successful binary, its execution is silently skipped. This allows the CI to test, to the fullest extent possible, incremental cross-compilation to targets whose binaries can't be executed on the host.
1 parent f60c045
Changed files (1)
tools/incr-check.zig
@@ -55,9 +55,6 @@ pub fn main() !void {
     const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int);
     const tmp_dir = try std.fs.cwd().makeOpenPath(tmp_dir_path, .{});
 
-    const child_prog_node = prog_node.start("zig build-exe", 0);
-    defer child_prog_node.end();
-
     // Convert paths to be relative to the cwd of the subprocess.
     const resolved_zig_exe = try std.fs.path.relative(arena, tmp_dir_path, zig_exe);
     const opt_resolved_lib_dir = if (opt_lib_dir) |lib_dir|
@@ -65,9 +62,18 @@ pub fn main() !void {
     else
         null;
 
+    const host = try std.zig.system.resolveTargetQuery(.{});
+
     const debug_log_verbose = debug_zcu or debug_link;
 
     for (case.targets) |target| {
+        const target_prog_node = node: {
+            var name_buf: [std.Progress.Node.max_name_len]u8 = undefined;
+            const name = std.fmt.bufPrint(&name_buf, "{s}-{s}", .{ target.query, @tagName(target.backend) }) catch &name_buf;
+            break :node prog_node.start(name, case.updates.len);
+        };
+        defer target_prog_node.end();
+
         if (debug_log_verbose) {
             std.log.scoped(.status).info("target: '{s}-{s}'", .{ target.query, @tagName(target.backend) });
         }
@@ -102,11 +108,14 @@ pub fn main() !void {
             try child_args.appendSlice(arena, &.{ "--debug-log", "link", "--debug-log", "link_state", "--debug-log", "link_relocs" });
         }
 
+        const zig_prog_node = target_prog_node.start("zig build-exe", 0);
+        defer zig_prog_node.end();
+
         var child = std.process.Child.init(child_args.items, arena);
         child.stdin_behavior = .Pipe;
         child.stdout_behavior = .Pipe;
         child.stderr_behavior = .Pipe;
-        child.progress_node = child_prog_node;
+        child.progress_node = zig_prog_node;
         child.cwd_dir = tmp_dir;
         child.cwd = tmp_dir_path;
 
@@ -131,6 +140,7 @@ pub fn main() !void {
         var eval: Eval = .{
             .arena = arena,
             .case = case,
+            .host = host,
             .target = target,
             .tmp_dir = tmp_dir,
             .tmp_dir_path = tmp_dir_path,
@@ -148,7 +158,7 @@ pub fn main() !void {
         defer poller.deinit();
 
         for (case.updates) |update| {
-            var update_node = prog_node.start(update.name, 0);
+            var update_node = target_prog_node.start(update.name, 0);
             defer update_node.end();
 
             if (debug_log_verbose) {
@@ -168,6 +178,7 @@ pub fn main() !void {
 
 const Eval = struct {
     arena: Allocator,
+    host: std.Target,
     case: Case,
     target: Case.Target,
     tmp_dir: std.fs.Dir,
@@ -270,14 +281,7 @@ const Eval = struct {
                     const name = std.fs.path.stem(std.fs.path.basename(eval.case.root_source_file));
                     const bin_name = try std.zig.binNameAlloc(arena, .{
                         .root_name = name,
-                        .target = try std.zig.system.resolveTargetQuery(try std.Build.parseTargetQuery(.{
-                            .arch_os_abi = eval.target.query,
-                            .object_format = switch (eval.target.backend) {
-                                .sema => unreachable,
-                                .selfhosted, .llvm => null,
-                                .cbe => "c",
-                            },
-                        })),
+                        .target = eval.target.resolved,
                         .output_mode = .Exe,
                     });
                     const bin_path = try std.fs.path.join(arena, &.{ result_dir, bin_name });
@@ -346,9 +350,41 @@ const Eval = struct {
             },
         };
 
+        var argv_buf: [2][]const u8 = undefined;
+        const argv: []const []const u8, const ignore_stderr: bool = switch (std.zig.system.getExternalExecutor(
+            eval.host,
+            &eval.target.resolved,
+            .{ .link_libc = eval.target.backend == .cbe },
+        )) {
+            .bad_dl, .bad_os_or_cpu => {
+                // This binary cannot be executed on this host.
+                if (eval.allow_stderr) {
+                    std.log.warn("skipping execution because host '{s}' cannot execute binaries for foreign target '{s}'", .{
+                        try eval.host.zigTriple(eval.arena),
+                        try eval.target.resolved.zigTriple(eval.arena),
+                    });
+                }
+                return;
+            },
+            .native, .rosetta => argv: {
+                argv_buf[0] = binary_path;
+                break :argv .{ argv_buf[0..1], false };
+            },
+            .qemu, .wine, .wasmtime, .darling => |executor_cmd| argv: {
+                argv_buf[0] = executor_cmd;
+                argv_buf[1] = binary_path;
+                // Some executors (looking at you, Wine) like throwing some stderr in, just for fun.
+                // Therefore, we'll ignore stderr when using a foreign executor.
+                break :argv .{ argv_buf[0..2], true };
+            },
+        };
+
+        const run_prog_node = prog_node.start("run generated executable", 0);
+        defer run_prog_node.end();
+
         const result = std.process.Child.run(.{
             .allocator = eval.arena,
-            .argv = &.{binary_path},
+            .argv = argv,
             .cwd_dir = eval.tmp_dir,
             .cwd = eval.tmp_dir_path,
         }) catch |err| {
@@ -356,7 +392,7 @@ const Eval = struct {
                 update.name, binary_path, @errorName(err),
             });
         };
-        if (result.stderr.len != 0) {
+        if (!ignore_stderr and result.stderr.len != 0) {
             std.log.err("update '{s}': generated executable '{s}' had unexpected stderr:\n{s}", .{
                 update.name, binary_path, result.stderr,
             });
@@ -380,7 +416,7 @@ const Eval = struct {
                 });
             },
         }
-        if (result.stderr.len != 0) std.process.exit(1);
+        if (!ignore_stderr and result.stderr.len != 0) std.process.exit(1);
     }
 
     fn requestUpdate(eval: *Eval) !void {
@@ -468,6 +504,7 @@ const Case = struct {
 
     const Target = struct {
         query: []const u8,
+        resolved: std.Target,
         backend: Backend,
         const Backend = enum {
             /// Run semantic analysis only. Runtime output will not be tested, but we still verify
@@ -529,12 +566,26 @@ const Case = struct {
                 } else if (std.mem.eql(u8, key, "target")) {
                     const split_idx = std.mem.lastIndexOfScalar(u8, val, '-') orelse
                         fatal("line {d}: target does not include backend", .{line_n});
+
                     const query = val[0..split_idx];
+
                     const backend_str = val[split_idx + 1 ..];
                     const backend: Target.Backend = std.meta.stringToEnum(Target.Backend, backend_str) orelse
                         fatal("line {d}: invalid backend '{s}'", .{ line_n, backend_str });
+
+                    const parsed_query = std.Build.parseTargetQuery(.{
+                        .arch_os_abi = query,
+                        .object_format = switch (backend) {
+                            .sema, .selfhosted, .llvm => null,
+                            .cbe => "c",
+                        },
+                    }) catch fatal("line {d}: invalid target query '{s}'", .{ line_n, query });
+
+                    const resolved = try std.zig.system.resolveTargetQuery(parsed_query);
+
                     try targets.append(arena, .{
                         .query = query,
+                        .resolved = resolved,
                         .backend = backend,
                     });
                 } else if (std.mem.eql(u8, key, "update")) {