Commit c7c7ad1b78
Changed files (1)
lib
compiler
lib/compiler/std-docs.zig
@@ -15,9 +15,8 @@ pub fn main() !void {
const zig_exe_path = args[2];
const global_cache_path = args[3];
- const docs_path = try std.fs.path.join(arena, &.{ zig_lib_directory, "docs" });
- var docs_dir = try std.fs.cwd().openDir(docs_path, .{});
- defer docs_dir.close();
+ var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{});
+ defer lib_dir.close();
const listen_port: u16 = 0;
const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;
@@ -29,6 +28,14 @@ pub fn main() !void {
std.log.err("unable to open browser: {s}", .{@errorName(err)});
};
+ var context: Context = .{
+ .gpa = gpa,
+ .zig_exe_path = zig_exe_path,
+ .global_cache_path = global_cache_path,
+ .lib_dir = lib_dir,
+ .zig_lib_directory = zig_lib_directory,
+ };
+
var read_buffer: [8000]u8 = undefined;
accept: while (true) {
const connection = try http_server.accept();
@@ -43,7 +50,7 @@ pub fn main() !void {
continue :accept;
},
};
- serveRequest(&request, gpa, docs_dir, zig_exe_path, global_cache_path) catch |err| {
+ serveRequest(&request, &context) catch |err| {
std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
continue :accept;
};
@@ -51,25 +58,31 @@ pub fn main() !void {
}
}
-fn serveRequest(
- request: *std.http.Server.Request,
+const Context = struct {
gpa: Allocator,
- docs_dir: std.fs.Dir,
+ lib_dir: std.fs.Dir,
+ zig_lib_directory: []const u8,
zig_exe_path: []const u8,
global_cache_path: []const u8,
-) !void {
+};
+
+fn serveRequest(request: *std.http.Server.Request, context: *Context) !void {
if (std.mem.eql(u8, request.head.target, "/") or
std.mem.eql(u8, request.head.target, "/debug/"))
{
- try serveDocsFile(request, gpa, docs_dir, "index.html", "text/html");
+ try serveDocsFile(request, context, "docs/index.html", "text/html");
} else if (std.mem.eql(u8, request.head.target, "/main.js") or
std.mem.eql(u8, request.head.target, "/debug/main.js"))
{
- try serveDocsFile(request, gpa, docs_dir, "main.js", "application/javascript");
+ try serveDocsFile(request, context, "docs/main.js", "application/javascript");
} else if (std.mem.eql(u8, request.head.target, "/main.wasm")) {
- try serveWasm(request, gpa, zig_exe_path, global_cache_path, .ReleaseFast);
+ try serveWasm(request, context, .ReleaseFast);
} else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) {
- try serveWasm(request, gpa, zig_exe_path, global_cache_path, .Debug);
+ try serveWasm(request, context, .Debug);
+ } else if (std.mem.eql(u8, request.head.target, "/sources.tar") or
+ std.mem.eql(u8, request.head.target, "/debug/sources.tar"))
+ {
+ try serveSourcesTar(request, context);
} else {
try request.respond("not found", .{
.status = .not_found,
@@ -80,68 +93,219 @@ fn serveRequest(
}
}
+const cache_control_header: std.http.Header = .{
+ .name = "cache-control",
+ .value = "max-age=0, must-revalidate",
+};
+
fn serveDocsFile(
request: *std.http.Server.Request,
- gpa: Allocator,
- docs_dir: std.fs.Dir,
+ context: *Context,
name: []const u8,
content_type: []const u8,
) !void {
+ const gpa = context.gpa;
// The desired API is actually sendfile, which will require enhancing std.http.Server.
// We load the file with every request so that the user can make changes to the file
// and refresh the HTML page without restarting this server.
- const file_contents = try docs_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);
+ const file_contents = try context.lib_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);
defer gpa.free(file_contents);
try request.respond(file_contents, .{
.status = .ok,
.extra_headers = &.{
.{ .name = "content-type", .value = content_type },
+ cache_control_header,
},
});
}
+fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
+ _ = request;
+ _ = context;
+ @panic("TODO");
+}
+
fn serveWasm(
request: *std.http.Server.Request,
- gpa: Allocator,
- zig_exe_path: []const u8,
- global_cache_path: []const u8,
+ context: *Context,
optimize_mode: std.builtin.OptimizeMode,
) !void {
- _ = request;
- _ = gpa;
- _ = zig_exe_path;
- _ = global_cache_path;
- _ = optimize_mode;
- @panic("TODO serve wasm");
+ const gpa = context.gpa;
+
+ var arena_instance = std.heap.ArenaAllocator.init(gpa);
+ defer arena_instance.deinit();
+ const arena = arena_instance.allocator();
+
+ // Do the compilation every request, so that the user can edit the files
+ // and see the changes without restarting the server.
+ const wasm_binary_path = try buildWasmBinary(arena, context, optimize_mode);
+ // std.http.Server does not have a sendfile API yet.
+ const file_contents = try std.fs.cwd().readFileAlloc(gpa, wasm_binary_path, 10 * 1024 * 1024);
+ defer gpa.free(file_contents);
+ try request.respond(file_contents, .{
+ .status = .ok,
+ .extra_headers = &.{
+ .{ .name = "content-type", .value = "application/wasm" },
+ cache_control_header,
+ },
+ });
}
-const BuildWasmBinaryOptions = struct {
- zig_exe_path: []const u8,
- global_cache_path: []const u8,
- main_src_path: []const u8,
-};
+fn buildWasmBinary(
+ arena: Allocator,
+ context: *Context,
+ optimize_mode: std.builtin.OptimizeMode,
+) ![]const u8 {
+ const gpa = context.gpa;
+
+ const main_src_path = try std.fs.path.join(arena, &.{
+ context.zig_lib_directory, "docs", "wasm", "main.zig",
+ });
-fn buildWasmBinary(arena: Allocator, options: BuildWasmBinaryOptions) ![]const u8 {
var argv: std.ArrayListUnmanaged([]const u8) = .{};
+
try argv.appendSlice(arena, &.{
- options.zig_exe_path,
+ context.zig_exe_path,
"build-exe",
"-fno-entry",
- "-OReleaseSmall",
+ "-O",
+ @tagName(optimize_mode),
"-target",
"wasm32-freestanding",
"-mcpu",
"baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext",
"--cache-dir",
- options.global_cache_path,
+ context.global_cache_path,
"--global-cache-dir",
- options.global_cache_path,
+ context.global_cache_path,
"--name",
"autodoc",
"-rdynamic",
- options.main_src_path,
+ main_src_path,
"--listen=-",
});
+
+ var child = std.ChildProcess.init(argv.items, gpa);
+ child.stdin_behavior = .Pipe;
+ child.stdout_behavior = .Pipe;
+ child.stderr_behavior = .Pipe;
+ try child.spawn();
+
+ var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
+ .stdout = child.stdout.?,
+ .stderr = child.stderr.?,
+ });
+ defer poller.deinit();
+
+ try sendMessage(child.stdin.?, .update);
+ try sendMessage(child.stdin.?, .exit);
+
+ const Header = std.zig.Server.Message.Header;
+ var result: ?[]const u8 = null;
+ var result_error_bundle = std.zig.ErrorBundle.empty;
+
+ const stdout = poller.fifo(.stdout);
+
+ poll: while (true) {
+ while (stdout.readableLength() < @sizeOf(Header)) {
+ if (!(try poller.poll())) break :poll;
+ }
+ const header = stdout.reader().readStruct(Header) catch unreachable;
+ while (stdout.readableLength() < header.bytes_len) {
+ if (!(try poller.poll())) break :poll;
+ }
+ const body = stdout.readableSliceOfLen(header.bytes_len);
+
+ switch (header.tag) {
+ .zig_version => {
+ if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
+ return error.ZigProtocolVersionMismatch;
+ }
+ },
+ .error_bundle => {
+ const EbHdr = std.zig.Server.Message.ErrorBundle;
+ const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
+ const extra_bytes =
+ body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
+ const string_bytes =
+ body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
+ // TODO: use @ptrCast when the compiler supports it
+ const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
+ const extra_array = try arena.alloc(u32, unaligned_extra.len);
+ @memcpy(extra_array, unaligned_extra);
+ result_error_bundle = .{
+ .string_bytes = try arena.dupe(u8, string_bytes),
+ .extra = extra_array,
+ };
+ },
+ .emit_bin_path => {
+ const EbpHdr = std.zig.Server.Message.EmitBinPath;
+ const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
+ if (!ebp_hdr.flags.cache_hit) {
+ std.log.info("source changes detected; rebuilding wasm component", .{});
+ }
+ result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
+ },
+ else => {}, // ignore other messages
+ }
+
+ stdout.discard(body.len);
+ }
+
+ const stderr = poller.fifo(.stderr);
+ if (stderr.readableLength() > 0) {
+ const owned_stderr = try stderr.toOwnedSlice();
+ defer gpa.free(owned_stderr);
+ std.debug.print("{s}", .{owned_stderr});
+ }
+
+ // Send EOF to stdin.
+ child.stdin.?.close();
+ child.stdin = null;
+
+ switch (try child.wait()) {
+ .Exited => |code| {
+ if (code != 0) {
+ std.log.err(
+ "the following command exited with error code {d}:\n{s}",
+ .{ code, try std.Build.Step.allocPrintCmd(arena, null, argv.items) },
+ );
+ return error.AlreadyReported;
+ }
+ },
+ .Signal, .Stopped, .Unknown => {
+ std.log.err(
+ "the following command terminated unexpectedly:\n{s}",
+ .{try std.Build.Step.allocPrintCmd(arena, null, argv.items)},
+ );
+ return error.AlreadyReported;
+ },
+ }
+
+ if (result_error_bundle.errorMessageCount() > 0) {
+ const color = std.zig.Color.auto;
+ result_error_bundle.renderToStdErr(color.renderOptions());
+ std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
+ result_error_bundle.errorMessageCount(),
+ try std.Build.Step.allocPrintCmd(arena, null, argv.items),
+ });
+ return error.AlreadyReported;
+ }
+
+ return result orelse {
+ std.log.err("child process failed to report result\n{s}", .{
+ try std.Build.Step.allocPrintCmd(arena, null, argv.items),
+ });
+ return error.AlreadyReported;
+ };
+}
+
+fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
+ const header: std.zig.Client.Message.Header = .{
+ .tag = tag,
+ .bytes_len = 0,
+ };
+ try file.writeAll(std.mem.asBytes(&header));
}
fn openBrowserTab(gpa: Allocator, url: []const u8) !void {