| ... | ... | @@ -0,0 +1,164 @@ |
| 1 | const builtin = @import("builtin"); |
| 2 | const std = @import("std"); |
| 3 | const Allocator = std.mem.Allocator; |
| 4 | |
| 5 | pub fn main() !void { |
| 6 | var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
| 7 | defer arena_instance.deinit(); |
| 8 | const arena = arena_instance.allocator(); |
| 9 | |
| 10 | var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{}; |
| 11 | const gpa = general_purpose_allocator.allocator(); |
| 12 | |
| 13 | const args = try std.process.argsAlloc(arena); |
| 14 | const zig_lib_directory = args[1]; |
| 15 | const zig_exe_path = args[2]; |
| 16 | const global_cache_path = args[3]; |
| 17 | |
| 18 | const docs_path = try std.fs.path.join(arena, &.{ zig_lib_directory, "docs" }); |
| 19 | var docs_dir = try std.fs.cwd().openDir(docs_path, .{}); |
| 20 | defer docs_dir.close(); |
| 21 | |
| 22 | const listen_port: u16 = 0; |
| 23 | const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable; |
| 24 | var http_server = try address.listen(.{}); |
| 25 | const port = http_server.listen_address.in.getPort(); |
| 26 | const url = try std.fmt.allocPrint(arena, "http://127.0.0.1:{d}/\n", .{port}); |
| 27 | std.io.getStdOut().writeAll(url) catch {}; |
| 28 | openBrowserTab(gpa, url[0 .. url.len - 1 :'\n']) catch |err| { |
| 29 | std.log.err("unable to open browser: {s}", .{@errorName(err)}); |
| 30 | }; |
| 31 | |
| 32 | var read_buffer: [8000]u8 = undefined; |
| 33 | accept: while (true) { |
| 34 | const connection = try http_server.accept(); |
| 35 | defer connection.stream.close(); |
| 36 | |
| 37 | var server = std.http.Server.init(connection, &read_buffer); |
| 38 | while (server.state == .ready) { |
| 39 | var request = server.receiveHead() catch |err| switch (err) { |
| 40 | error.HttpConnectionClosing => continue :accept, |
| 41 | else => { |
| 42 | std.log.err("closing http connection: {s}", .{@errorName(err)}); |
| 43 | continue :accept; |
| 44 | }, |
| 45 | }; |
| 46 | serveRequest(&request, gpa, docs_dir, zig_exe_path, global_cache_path) catch |err| { |
| 47 | std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) }); |
| 48 | continue :accept; |
| 49 | }; |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | fn serveRequest( |
| 55 | request: *std.http.Server.Request, |
| 56 | gpa: Allocator, |
| 57 | docs_dir: std.fs.Dir, |
| 58 | zig_exe_path: []const u8, |
| 59 | global_cache_path: []const u8, |
| 60 | ) !void { |
| 61 | if (std.mem.eql(u8, request.head.target, "/") or |
| 62 | std.mem.eql(u8, request.head.target, "/debug/")) |
| 63 | { |
| 64 | try serveDocsFile(request, gpa, docs_dir, "index.html", "text/html"); |
| 65 | } else if (std.mem.eql(u8, request.head.target, "/main.js") or |
| 66 | std.mem.eql(u8, request.head.target, "/debug/main.js")) |
| 67 | { |
| 68 | try serveDocsFile(request, gpa, docs_dir, "main.js", "application/javascript"); |
| 69 | } else if (std.mem.eql(u8, request.head.target, "/main.wasm")) { |
| 70 | try serveWasm(request, gpa, zig_exe_path, global_cache_path, .ReleaseFast); |
| 71 | } else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) { |
| 72 | try serveWasm(request, gpa, zig_exe_path, global_cache_path, .Debug); |
| 73 | } else { |
| 74 | try request.respond("not found", .{ |
| 75 | .status = .not_found, |
| 76 | .extra_headers = &.{ |
| 77 | .{ .name = "content-type", .value = "text/plain" }, |
| 78 | }, |
| 79 | }); |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | fn serveDocsFile( |
| 84 | request: *std.http.Server.Request, |
| 85 | gpa: Allocator, |
| 86 | docs_dir: std.fs.Dir, |
| 87 | name: []const u8, |
| 88 | content_type: []const u8, |
| 89 | ) !void { |
| 90 | // 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 file |
| 92 | // and refresh the HTML page without restarting this server. |
| 93 | const file_contents = try docs_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024); |
| 94 | defer gpa.free(file_contents); |
| 95 | try request.respond(file_contents, .{ |
| 96 | .status = .ok, |
| 97 | .extra_headers = &.{ |
| 98 | .{ .name = "content-type", .value = content_type }, |
| 99 | }, |
| 100 | }); |
| 101 | } |
| 102 | |
| 103 | fn serveWasm( |
| 104 | request: *std.http.Server.Request, |
| 105 | gpa: Allocator, |
| 106 | zig_exe_path: []const u8, |
| 107 | global_cache_path: []const u8, |
| 108 | optimize_mode: std.builtin.OptimizeMode, |
| 109 | ) !void { |
| 110 | _ = request; |
| 111 | _ = gpa; |
| 112 | _ = zig_exe_path; |
| 113 | _ = global_cache_path; |
| 114 | _ = optimize_mode; |
| 115 | @panic("TODO serve wasm"); |
| 116 | } |
| 117 | |
| 118 | const BuildWasmBinaryOptions = struct { |
| 119 | zig_exe_path: []const u8, |
| 120 | global_cache_path: []const u8, |
| 121 | main_src_path: []const u8, |
| 122 | }; |
| 123 | |
| 124 | fn buildWasmBinary(arena: Allocator, options: BuildWasmBinaryOptions) ![]const u8 { |
| 125 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; |
| 126 | try argv.appendSlice(arena, &.{ |
| 127 | options.zig_exe_path, |
| 128 | "build-exe", |
| 129 | "-fno-entry", |
| 130 | "-OReleaseSmall", |
| 131 | "-target", |
| 132 | "wasm32-freestanding", |
| 133 | "-mcpu", |
| 134 | "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext", |
| 135 | "--cache-dir", |
| 136 | options.global_cache_path, |
| 137 | "--global-cache-dir", |
| 138 | options.global_cache_path, |
| 139 | "--name", |
| 140 | "autodoc", |
| 141 | "-rdynamic", |
| 142 | options.main_src_path, |
| 143 | "--listen=-", |
| 144 | }); |
| 145 | } |
| 146 | |
| 147 | fn openBrowserTab(gpa: Allocator, url: []const u8) !void { |
| 148 | // Until https://github.com/ziglang/zig/issues/19205 is implemented, we |
| 149 | // spawn a thread for this child process. |
| 150 | _ = try std.Thread.spawn(.{}, openBrowserTabThread, .{ gpa, url }); |
| 151 | } |
| 152 | |
| 153 | fn openBrowserTabThread(gpa: Allocator, url: []const u8) !void { |
| 154 | const main_exe = switch (builtin.os.tag) { |
| 155 | .windows => "explorer", |
| 156 | else => "xdg-open", |
| 157 | }; |
| 158 | var child = std.ChildProcess.init(&.{ main_exe, url }, gpa); |
| 159 | child.stdin_behavior = .Ignore; |
| 160 | child.stdout_behavior = .Ignore; |
| 161 | child.stderr_behavior = .Ignore; |
| 162 | try child.spawn(); |
| 163 | _ = try child.wait(); |
| 164 | } |