in: std.fs.File,
out: std.fs.File,
receive_fifo: std.fifo.LinearFifo(u8, .Dynamic),
pub const Message = struct {
pub const Header = extern struct {
tag: Tag,
bytes_len: u32,
};
pub const Tag = enum(u32) {
zig_version,
error_bundle,
progress,
emit_bin_path,
test_metadata,
test_results,
_,
};
pub const ErrorBundle = extern struct {
extra_len: u32,
string_bytes_len: u32,
};
pub const TestMetadata = extern struct {
string_bytes_len: u32,
tests_len: u32,
};
pub const TestResults = extern struct {
index: u32,
flags: Flags,
pub const Flags = packed struct(u8) {
fail: bool,
skip: bool,
leak: bool,
reserved: u5 = 0,
};
};
pub const EmitBinPath = extern struct {
flags: Flags,
pub const Flags = packed struct(u8) {
cache_hit: bool,
reserved: u7 = 0,
};
};
};
pub const Options = struct {
gpa: Allocator,
in: std.fs.File,
out: std.fs.File,
zig_version: []const u8,
};
pub fn init(options: Options) !Server {
var s: Server = .{
.in = options.in,
.out = options.out,
.receive_fifo = std.fifo.LinearFifo(u8, .Dynamic).init(options.gpa),
};
try s.serveStringMessage(.zig_version, options.zig_version);
return s;
}
pub fn deinit(s: *Server) void {
s.receive_fifo.deinit();
s.* = undefined;
}
pub fn receiveMessage(s: *Server) !InMessage.Header {
const Header = InMessage.Header;
const fifo = &s.receive_fifo;
while (true) {
const buf = fifo.readableSlice(0);
assert(fifo.readableLength() == buf.len);
if (buf.len >= @sizeOf(Header)) {
const bytes_len = bswap_and_workaround_u32(buf[4..][0..4]);
const tag = bswap_and_workaround_tag(buf[0..][0..4]);
if (buf.len - @sizeOf(Header) >= bytes_len) {
fifo.discard(@sizeOf(Header));
return .{
.tag = tag,
.bytes_len = bytes_len,
};
} else {
const needed = bytes_len - (buf.len - @sizeOf(Header));
const write_buffer = try fifo.writableWithSize(needed);
const amt = try s.in.read(write_buffer);
fifo.update(amt);
continue;
}
}
const write_buffer = try fifo.writableWithSize(256);
const amt = try s.in.read(write_buffer);
fifo.update(amt);
}
}
pub fn receiveBody_u32(s: *Server) !u32 {
const fifo = &s.receive_fifo;
const buf = fifo.readableSlice(0);
const result = @ptrCast(*align(1) const u32, buf[0..4]).*;
fifo.discard(4);
return bswap(result);
}
pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void {
return s.serveMessage(.{
.tag = tag,
.bytes_len = @intCast(u32, msg.len),
}, &.{msg});
}
pub fn serveMessage(
s: *const Server,
header: OutMessage.Header,
bufs: []const []const u8,
) !void {
var iovecs: [10]std.os.iovec_const = undefined;
const header_le = bswap(header);
iovecs[0] = .{
.iov_base = @ptrCast([*]const u8, &header_le),
.iov_len = @sizeOf(OutMessage.Header),
};
for (bufs, iovecs[1 .. bufs.len + 1]) |buf, *iovec| {
iovec.* = .{
.iov_base = buf.ptr,
.iov_len = buf.len,
};
}
try s.out.writevAll(iovecs[0 .. bufs.len + 1]);
}
pub fn serveEmitBinPath(
s: *Server,
fs_path: []const u8,
header: OutMessage.EmitBinPath,
) !void {
try s.serveMessage(.{
.tag = .emit_bin_path,
.bytes_len = @intCast(u32, fs_path.len + @sizeOf(OutMessage.EmitBinPath)),
}, &.{
std.mem.asBytes(&header),
fs_path,
});
}
pub fn serveTestResults(
s: *Server,
msg: OutMessage.TestResults,
) !void {
const msg_le = bswap(msg);
try s.serveMessage(.{
.tag = .test_results,
.bytes_len = @intCast(u32, @sizeOf(OutMessage.TestResults)),
}, &.{
std.mem.asBytes(&msg_le),
});
}
pub fn serveErrorBundle(s: *Server, error_bundle: std.zig.ErrorBundle) !void {
const eb_hdr: OutMessage.ErrorBundle = .{
.extra_len = @intCast(u32, error_bundle.extra.len),
.string_bytes_len = @intCast(u32, error_bundle.string_bytes.len),
};
const bytes_len = @sizeOf(OutMessage.ErrorBundle) +
4 * error_bundle.extra.len + error_bundle.string_bytes.len;
try s.serveMessage(.{
.tag = .error_bundle,
.bytes_len = @intCast(u32, bytes_len),
}, &.{
std.mem.asBytes(&eb_hdr),
std.mem.sliceAsBytes(error_bundle.extra),
error_bundle.string_bytes,
});
}
pub const TestMetadata = struct {
names: []u32,
async_frame_sizes: []u32,
expected_panic_msgs: []u32,
string_bytes: []const u8,
};
pub fn serveTestMetadata(s: *Server, test_metadata: TestMetadata) !void {
const header: OutMessage.TestMetadata = .{
.tests_len = bswap(@intCast(u32, test_metadata.names.len)),
.string_bytes_len = bswap(@intCast(u32, test_metadata.string_bytes.len)),
};
const bytes_len = @sizeOf(OutMessage.TestMetadata) +
3 * 4 * test_metadata.names.len + test_metadata.string_bytes.len;
if (need_bswap) {
bswap_u32_array(test_metadata.names);
bswap_u32_array(test_metadata.async_frame_sizes);
bswap_u32_array(test_metadata.expected_panic_msgs);
}
defer if (need_bswap) {
bswap_u32_array(test_metadata.names);
bswap_u32_array(test_metadata.async_frame_sizes);
bswap_u32_array(test_metadata.expected_panic_msgs);
};
return s.serveMessage(.{
.tag = .test_metadata,
.bytes_len = @intCast(u32, bytes_len),
}, &.{
std.mem.asBytes(&header),
std.mem.sliceAsBytes(test_metadata.names),
std.mem.sliceAsBytes(test_metadata.async_frame_sizes),
std.mem.sliceAsBytes(test_metadata.expected_panic_msgs),
test_metadata.string_bytes,
});
}
fn bswap(x: anytype) @TypeOf(x) {
if (!need_bswap) return x;
const T = @TypeOf(x);
switch (@typeInfo(T)) {
.Enum => return @intToEnum(T, @byteSwap(@enumToInt(x))),
.Int => return @byteSwap(x),
.Struct => |info| switch (info.layout) {
.Extern => {
var result: T = undefined;
inline for (info.fields) |field| {
@field(result, field.name) = bswap(@field(x, field.name));
}
return result;
},
.Packed => {
const I = info.backing_integer.?;
return @bitCast(T, @byteSwap(@bitCast(I, x)));
},
.Auto => @compileError("auto layout struct"),
},
else => @compileError("bswap on type " ++ @typeName(T)),
}
}
fn bswap_u32_array(slice: []u32) void {
comptime assert(need_bswap);
for (slice) |*elem| elem.* = @byteSwap(elem.*);
}
fn bswap_and_workaround_u32(bytes_ptr: *const [4]u8) u32 {
return std.mem.readIntLittle(u32, bytes_ptr);
}
fn bswap_and_workaround_tag(bytes_ptr: *const [4]u8) InMessage.Tag {
const int = std.mem.readIntLittle(u32, bytes_ptr);
return @intToEnum(InMessage.Tag, int);
}
const OutMessage = std.zig.Server.Message;
const InMessage = std.zig.Client.Message;
const Server = @This();
const builtin = @import("builtin");
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const native_endian = builtin.target.cpu.arch.endian();
const need_bswap = native_endian != .Little;