pub fn buildExeSubprocess(
gpa: Allocator,
io: Io,
options: BuildExeSubprocessOptions,
) BuildExeSubprocessError!BuildExeSubprocessResult {
const cmd: SubprocessCommand = .{ .argv = options.argv };
var child = std.process.spawn(io, .{
.argv = options.argv,
.environ_map = options.environ_map,
.stdin = .pipe,
.stdout = .pipe,
.stderr = .pipe,
.progress_node = options.progress_node,
}) catch |err| {
log.err("spawning command {t}: {f}", .{ err, cmd });
return error.AlreadyReported;
};
defer child.kill(io);
var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
var multi_reader: Io.File.MultiReader = undefined;
multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
defer multi_reader.deinit();
const stdout = multi_reader.reader(0);
const stderr = multi_reader.reader(1);
var stdin_buffer: [8]u8 = undefined;
var stdin_writer = child.stdin.?.writerStreaming(io, &stdin_buffer);
var client: Client = .{
.in = stdout,
.out = &stdin_writer.interface,
};
(blk: {
client.serveMessageHeader(.{ .tag = .update, .bytes_len = 0 }) catch |err| break :blk err;
client.serveMessageHeader(.{ .tag = .exit, .bytes_len = 0 }) catch |err| break :blk err;
client.out.flush() catch |err| break :blk err;
}) catch |err| switch (err) {
error.WriteFailed => {
if (stdin_writer.err.? == error.Canceled) return error.Canceled;
log.err("{t} writing to command: {f}", .{ stdin_writer.err.?, cmd });
return error.AlreadyReported;
},
};
var result: ?Cache.Path = null;
defer if (result) |r| gpa.free(r.sub_path);
var result_error_bundle: ErrorBundle = .empty;
defer result_error_bundle.deinit(gpa);
var received_fs_inputs = false;
var cache_hit = false;
var eos_err: error{EndOfStream}!void = {};
while (true) {
const header = client.receiveMessageWithMultiReader(&multi_reader, .none) catch |err| switch (err) {
error.Timeout => unreachable,
error.EndOfStream => |e| {
if (client.in.bufferedLen() == 0) break;
// Better to report the crash with stderr below, but we set
// this in case the child exits successfully while violating
// this protocol.
eos_err = e;
break;
},
error.Canceled, error.OutOfMemory => |e| return e,
else => |e| {
log.err("{t} reading from command: {f}", .{ e, cmd });
return error.AlreadyReported;
},
};
const body = stdout.take(header.bytes_len) catch unreachable;
switch (header.tag) {
.zig_version => {
if (!mem.eql(u8, builtin.zig_version_string, body)) {
log.err("zig protocol version mismatch from command: {f}", .{cmd});
return error.AlreadyReported;
}
},
.error_bundle => {
result_error_bundle.deinit(gpa);
result_error_bundle = Server.allocErrorBundle(gpa, body) catch |err| switch (err) {
error.EndOfStream => break,
else => |e| return e,
};
},
.emit_digest => {
const EmitDigest = Server.Message.EmitDigest;
const ebp_hdr: *align(1) const EmitDigest = @ptrCast(body);
cache_hit = ebp_hdr.flags.cache_hit;
const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
if (result) |r| gpa.free(r.sub_path);
result = .{
.root_dir = options.cache_root,
.sub_path = try Dir.path.join(gpa, &.{ "o", &Cache.binToHex(digest.*) }),
};
},
.file_system_inputs => if (options.cache_manifest) |man| {
received_fs_inputs = true;
var it = mem.splitScalar(u8, body, 0);
while (it.next()) |prefixed_path| {
const prefix: Server.Message.PathPrefix = @fromBackingInt(@intCast(prefixed_path[0] - 1));
const sub_path = try gpa.dupe(u8, prefixed_path[1..]);
var keep = false;
defer if (!keep) gpa.free(sub_path);
keep = man.addPrefixedPathPost(.{
.prefix = @backingInt(prefix),
.sub_path = sub_path,
}) catch |err| switch (err) {
error.Canceled, error.OutOfMemory => |e| return e,
else => |e| {
log.err("adding {t} {s} to cache failed: {t}", .{ prefix, sub_path, e });
return error.AlreadyReported;
},
};
}
},
else => {}, // ignore other messages
}
}
const stderr_contents = stderr.buffered();
if (stderr_contents.len > 0)
log.warn("unexpected stderr from {s} command:\n{s}", .{ options.argv[0], stderr_contents });
eos_err catch {
log.err("unexpected end of stream from command: {f}", .{cmd});
return error.AlreadyReported;
};
// Send EOF to stdin.
child.stdin.?.close(io);
child.stdin = null;
const term = child.wait(io) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| {
log.err("{t} waiting for command: {f}", .{ e, cmd });
return error.AlreadyReported;
},
};
if (!term.success()) {
log.err("command {f}: {f}", .{ term, cmd });
if (received_fs_inputs) return error.FailedButCacheIntact;
return error.AlreadyReported;
}
if (result_error_bundle.errorMessageCount() > 0) {
result_error_bundle.renderToStderr(io, .{}, .auto) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| {
log.err("failed rendering error bundle: {t}", .{e});
return error.AlreadyReported;
},
};
if (!options.skip_log_cmdline_on_compile_errors) log.err("command reported {d} compilation errors: {f}", .{
result_error_bundle.errorMessageCount(), cmd,
});
if (received_fs_inputs) return error.FailedButCacheIntact;
return error.AlreadyReported;
}
const base_path = result orelse {
log.err("command failed to report result: {f}", .{cmd});
return error.AlreadyReported;
};
const parsed_target = system.resolveTargetQuery(io, std.Build.parseTargetQuery(.{
.arch_os_abi = options.arch_os_abi orelse "native",
.cpu_features = options.cpu_features,
}) catch unreachable) catch unreachable;
const bin_name = try binNameAlloc(gpa, .{
.root_name = options.root_name,
.cpu_arch = parsed_target.cpu.arch,
.os_tag = parsed_target.os.tag,
.ofmt = parsed_target.ofmt,
.abi = parsed_target.abi,
.output_mode = .Exe,
});
defer gpa.free(bin_name);
return .{
.received_fs_inputs = received_fs_inputs,
.cache_hit = cache_hit,
.path = try base_path.join(gpa, bin_name),
};
}
Assumes
argvhas--listen=-in it and the child process iszig build-exe.Result path is allocated via gpa.