authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-06 17:51:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-10 17:51:06-07:00
logc7c7ad1b78fc8f79b5a0ddebdd374630f272be9a
tree6b2e3367a359ed67e4be82f8b0a461f9a92eb2db
parent34faf9d12e3ce7b203a8e6e51e1652a985cfb626

zig std: implement serving the wasm binary


1 files changed, 199 insertions(+), 35 deletions(-)

lib/compiler/std-docs.zig+199-35
...@@ -15,9 +15,8 @@ pub fn main() !void {...@@ -15,9 +15,8 @@ pub fn main() !void {
15 const zig_exe_path = args[2];15 const zig_exe_path = args[2];
16 const global_cache_path = args[3];16 const global_cache_path = args[3];
1717
18 const docs_path = try std.fs.path.join(arena, &.{ zig_lib_directory, "docs" });18 var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{});
19 var docs_dir = try std.fs.cwd().openDir(docs_path, .{});19 defer lib_dir.close();
20 defer docs_dir.close();
2120
22 const listen_port: u16 = 0;21 const listen_port: u16 = 0;
23 const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;22 const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;
...@@ -29,6 +28,14 @@ pub fn main() !void {...@@ -29,6 +28,14 @@ pub fn main() !void {
29 std.log.err("unable to open browser: {s}", .{@errorName(err)});28 std.log.err("unable to open browser: {s}", .{@errorName(err)});
30 };29 };
3130
31 var context: Context = .{
32 .gpa = gpa,
33 .zig_exe_path = zig_exe_path,
34 .global_cache_path = global_cache_path,
35 .lib_dir = lib_dir,
36 .zig_lib_directory = zig_lib_directory,
37 };
38
32 var read_buffer: [8000]u8 = undefined;39 var read_buffer: [8000]u8 = undefined;
33 accept: while (true) {40 accept: while (true) {
34 const connection = try http_server.accept();41 const connection = try http_server.accept();
...@@ -43,7 +50,7 @@ pub fn main() !void {...@@ -43,7 +50,7 @@ pub fn main() !void {
43 continue :accept;50 continue :accept;
44 },51 },
45 };52 };
46 serveRequest(&request, gpa, docs_dir, zig_exe_path, global_cache_path) catch |err| {53 serveRequest(&request, &context) catch |err| {
47 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });54 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
48 continue :accept;55 continue :accept;
49 };56 };
...@@ -51,25 +58,31 @@ pub fn main() !void {...@@ -51,25 +58,31 @@ pub fn main() !void {
51 }58 }
52}59}
5360
54fn serveRequest(61const Context = struct {
55 request: *std.http.Server.Request,
56 gpa: Allocator,62 gpa: Allocator,
57 docs_dir: std.fs.Dir,63 lib_dir: std.fs.Dir,
64 zig_lib_directory: []const u8,
58 zig_exe_path: []const u8,65 zig_exe_path: []const u8,
59 global_cache_path: []const u8,66 global_cache_path: []const u8,
60) !void {67};
68
69fn serveRequest(request: *std.http.Server.Request, context: *Context) !void {
61 if (std.mem.eql(u8, request.head.target, "/") or70 if (std.mem.eql(u8, request.head.target, "/") or
62 std.mem.eql(u8, request.head.target, "/debug/"))71 std.mem.eql(u8, request.head.target, "/debug/"))
63 {72 {
64 try serveDocsFile(request, gpa, docs_dir, "index.html", "text/html");73 try serveDocsFile(request, context, "docs/index.html", "text/html");
65 } else if (std.mem.eql(u8, request.head.target, "/main.js") or74 } else if (std.mem.eql(u8, request.head.target, "/main.js") or
66 std.mem.eql(u8, request.head.target, "/debug/main.js"))75 std.mem.eql(u8, request.head.target, "/debug/main.js"))
67 {76 {
68 try serveDocsFile(request, gpa, docs_dir, "main.js", "application/javascript");77 try serveDocsFile(request, context, "docs/main.js", "application/javascript");
69 } else if (std.mem.eql(u8, request.head.target, "/main.wasm")) {78 } else if (std.mem.eql(u8, request.head.target, "/main.wasm")) {
70 try serveWasm(request, gpa, zig_exe_path, global_cache_path, .ReleaseFast);79 try serveWasm(request, context, .ReleaseFast);
71 } else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) {80 } else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) {
72 try serveWasm(request, gpa, zig_exe_path, global_cache_path, .Debug);81 try serveWasm(request, context, .Debug);
82 } else if (std.mem.eql(u8, request.head.target, "/sources.tar") or
83 std.mem.eql(u8, request.head.target, "/debug/sources.tar"))
84 {
85 try serveSourcesTar(request, context);
73 } else {86 } else {
74 try request.respond("not found", .{87 try request.respond("not found", .{
75 .status = .not_found,88 .status = .not_found,
...@@ -80,68 +93,219 @@ fn serveRequest(...@@ -80,68 +93,219 @@ fn serveRequest(
80 }93 }
81}94}
8295
96const cache_control_header: std.http.Header = .{
97 .name = "cache-control",
98 .value = "max-age=0, must-revalidate",
99};
100
83fn serveDocsFile(101fn serveDocsFile(
84 request: *std.http.Server.Request,102 request: *std.http.Server.Request,
85 gpa: Allocator,103 context: *Context,
86 docs_dir: std.fs.Dir,
87 name: []const u8,104 name: []const u8,
88 content_type: []const u8,105 content_type: []const u8,
89) !void {106) !void {
107 const gpa = context.gpa;
90 // The desired API is actually sendfile, which will require enhancing std.http.Server.108 // The desired API is actually sendfile, which will require enhancing std.http.Server.
91 // We load the file with every request so that the user can make changes to the file109 // We load the file with every request so that the user can make changes to the file
92 // and refresh the HTML page without restarting this server.110 // and refresh the HTML page without restarting this server.
93 const file_contents = try docs_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);111 const file_contents = try context.lib_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);
94 defer gpa.free(file_contents);112 defer gpa.free(file_contents);
95 try request.respond(file_contents, .{113 try request.respond(file_contents, .{
96 .status = .ok,114 .status = .ok,
97 .extra_headers = &.{115 .extra_headers = &.{
98 .{ .name = "content-type", .value = content_type },116 .{ .name = "content-type", .value = content_type },
117 cache_control_header,
99 },118 },
100 });119 });
101}120}
102121
122fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
123 _ = request;
124 _ = context;
125 @panic("TODO");
126}
127
103fn serveWasm(128fn serveWasm(
104 request: *std.http.Server.Request,129 request: *std.http.Server.Request,
105 gpa: Allocator,130 context: *Context,
106 zig_exe_path: []const u8,
107 global_cache_path: []const u8,
108 optimize_mode: std.builtin.OptimizeMode,131 optimize_mode: std.builtin.OptimizeMode,
109) !void {132) !void {
110 _ = request;133 const gpa = context.gpa;
111 _ = gpa;134
112 _ = zig_exe_path;135 var arena_instance = std.heap.ArenaAllocator.init(gpa);
113 _ = global_cache_path;136 defer arena_instance.deinit();
114 _ = optimize_mode;137 const arena = arena_instance.allocator();
115 @panic("TODO serve wasm");138
139 // Do the compilation every request, so that the user can edit the files
140 // and see the changes without restarting the server.
141 const wasm_binary_path = try buildWasmBinary(arena, context, optimize_mode);
142 // std.http.Server does not have a sendfile API yet.
143 const file_contents = try std.fs.cwd().readFileAlloc(gpa, wasm_binary_path, 10 * 1024 * 1024);
144 defer gpa.free(file_contents);
145 try request.respond(file_contents, .{
146 .status = .ok,
147 .extra_headers = &.{
148 .{ .name = "content-type", .value = "application/wasm" },
149 cache_control_header,
150 },
151 });
116}152}
117153
118const BuildWasmBinaryOptions = struct {154fn buildWasmBinary(
119 zig_exe_path: []const u8,155 arena: Allocator,
120 global_cache_path: []const u8,156 context: *Context,
121 main_src_path: []const u8,157 optimize_mode: std.builtin.OptimizeMode,
122};158) ![]const u8 {
159 const gpa = context.gpa;
160
161 const main_src_path = try std.fs.path.join(arena, &.{
162 context.zig_lib_directory, "docs", "wasm", "main.zig",
163 });
123164
124fn buildWasmBinary(arena: Allocator, options: BuildWasmBinaryOptions) ![]const u8 {
125 var argv: std.ArrayListUnmanaged([]const u8) = .{};165 var argv: std.ArrayListUnmanaged([]const u8) = .{};
166
126 try argv.appendSlice(arena, &.{167 try argv.appendSlice(arena, &.{
127 options.zig_exe_path,168 context.zig_exe_path,
128 "build-exe",169 "build-exe",
129 "-fno-entry",170 "-fno-entry",
130 "-OReleaseSmall",171 "-O",
172 @tagName(optimize_mode),
131 "-target",173 "-target",
132 "wasm32-freestanding",174 "wasm32-freestanding",
133 "-mcpu",175 "-mcpu",
134 "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext",176 "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext",
135 "--cache-dir",177 "--cache-dir",
136 options.global_cache_path,178 context.global_cache_path,
137 "--global-cache-dir",179 "--global-cache-dir",
138 options.global_cache_path,180 context.global_cache_path,
139 "--name",181 "--name",
140 "autodoc",182 "autodoc",
141 "-rdynamic",183 "-rdynamic",
142 options.main_src_path,184 main_src_path,
143 "--listen=-",185 "--listen=-",
144 });186 });
187
188 var child = std.ChildProcess.init(argv.items, gpa);
189 child.stdin_behavior = .Pipe;
190 child.stdout_behavior = .Pipe;
191 child.stderr_behavior = .Pipe;
192 try child.spawn();
193
194 var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
195 .stdout = child.stdout.?,
196 .stderr = child.stderr.?,
197 });
198 defer poller.deinit();
199
200 try sendMessage(child.stdin.?, .update);
201 try sendMessage(child.stdin.?, .exit);
202
203 const Header = std.zig.Server.Message.Header;
204 var result: ?[]const u8 = null;
205 var result_error_bundle = std.zig.ErrorBundle.empty;
206
207 const stdout = poller.fifo(.stdout);
208
209 poll: while (true) {
210 while (stdout.readableLength() < @sizeOf(Header)) {
211 if (!(try poller.poll())) break :poll;
212 }
213 const header = stdout.reader().readStruct(Header) catch unreachable;
214 while (stdout.readableLength() < header.bytes_len) {
215 if (!(try poller.poll())) break :poll;
216 }
217 const body = stdout.readableSliceOfLen(header.bytes_len);
218
219 switch (header.tag) {
220 .zig_version => {
221 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
222 return error.ZigProtocolVersionMismatch;
223 }
224 },
225 .error_bundle => {
226 const EbHdr = std.zig.Server.Message.ErrorBundle;
227 const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
228 const extra_bytes =
229 body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
230 const string_bytes =
231 body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
232 // TODO: use @ptrCast when the compiler supports it
233 const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
234 const extra_array = try arena.alloc(u32, unaligned_extra.len);
235 @memcpy(extra_array, unaligned_extra);
236 result_error_bundle = .{
237 .string_bytes = try arena.dupe(u8, string_bytes),
238 .extra = extra_array,
239 };
240 },
241 .emit_bin_path => {
242 const EbpHdr = std.zig.Server.Message.EmitBinPath;
243 const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
244 if (!ebp_hdr.flags.cache_hit) {
245 std.log.info("source changes detected; rebuilding wasm component", .{});
246 }
247 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
248 },
249 else => {}, // ignore other messages
250 }
251
252 stdout.discard(body.len);
253 }
254
255 const stderr = poller.fifo(.stderr);
256 if (stderr.readableLength() > 0) {
257 const owned_stderr = try stderr.toOwnedSlice();
258 defer gpa.free(owned_stderr);
259 std.debug.print("{s}", .{owned_stderr});
260 }
261
262 // Send EOF to stdin.
263 child.stdin.?.close();
264 child.stdin = null;
265
266 switch (try child.wait()) {
267 .Exited => |code| {
268 if (code != 0) {
269 std.log.err(
270 "the following command exited with error code {d}:\n{s}",
271 .{ code, try std.Build.Step.allocPrintCmd(arena, null, argv.items) },
272 );
273 return error.AlreadyReported;
274 }
275 },
276 .Signal, .Stopped, .Unknown => {
277 std.log.err(
278 "the following command terminated unexpectedly:\n{s}",
279 .{try std.Build.Step.allocPrintCmd(arena, null, argv.items)},
280 );
281 return error.AlreadyReported;
282 },
283 }
284
285 if (result_error_bundle.errorMessageCount() > 0) {
286 const color = std.zig.Color.auto;
287 result_error_bundle.renderToStdErr(color.renderOptions());
288 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
289 result_error_bundle.errorMessageCount(),
290 try std.Build.Step.allocPrintCmd(arena, null, argv.items),
291 });
292 return error.AlreadyReported;
293 }
294
295 return result orelse {
296 std.log.err("child process failed to report result\n{s}", .{
297 try std.Build.Step.allocPrintCmd(arena, null, argv.items),
298 });
299 return error.AlreadyReported;
300 };
301}
302
303fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
304 const header: std.zig.Client.Message.Header = .{
305 .tag = tag,
306 .bytes_len = 0,
307 };
308 try file.writeAll(std.mem.asBytes(&header));
145}309}
146310
147fn openBrowserTab(gpa: Allocator, url: []const u8) !void {311fn openBrowserTab(gpa: Allocator, url: []const u8) !void {