diff options
Diffstat (limited to 'build.zig')
| -rw-r--r-- | build.zig | 105 |
1 files changed, 49 insertions, 56 deletions
@@ -15,7 +15,6 @@ const std = @import("std"); const image = @import("tools/image.zig"); const serial = @import("tools/serial.zig"); const rom = @import("tools/rom.zig"); -const console = @import("tools/console.zig"); pub fn build(b: *std.Build) void { // ---------------------------------------------------------------- board and target knobs @@ -358,14 +357,45 @@ pub fn build(b: *std.Build) void { "console-baud", "interactive console baud (default 115200, the rate the bootloader leaves UART0 at)", ) orelse .b115200; - const con = ConsoleStep.create(b, port_path, console_baud); - b.step("console", "attach an interactive terminal to the running application").dependOn(&con.step); + + // A real host binary, and not an in-process step like every other tool here, for two reasons. + // It runs with no build runner at all when the board is already flashed; and as a child process + // under `Step.Run` with inherited stdio it gets std's stderr lock held for its whole lifetime + // (std/Build/Step/Run.zig:1588-1592), which is what stops `std.Progress` repainting the step + // tree over an interactive session. The in-process step this replaced never took that lock, and + // shredded the editor's screen with fragments of `[11/13] steps`. + const con_exe = b.addExecutable(.{ + .name = "p4-console", + .root_module = b.createModule(.{ + .root_source_file = b.path("tools/console_main.zig"), + .target = b.graph.host, + .optimize = .ReleaseSafe, + }), + }); + // Installed by the console steps, and deliberately NOT by `install`: the default build still + // lands exactly one file in zig-out, the flashable image. Anyone who has attached once has + // zig-out/bin/p4-console afterwards, which is the copy to run when the board is already + // flashed and no build is wanted. + const con_install = b.addInstallArtifact(con_exe, .{}); + + const con_args: []const []const u8 = &.{ "--port", port_path, "--baud", b.fmt("{d}", .{console_baud.rate()}) }; + + const con = b.addRunArtifact(con_exe); + con.addArgs(con_args); + // Inherited stdio is the whole point: the board's bytes and the user's keystrokes pass through + // untouched, and the terminal the child sees is the real one, so its ioctls answer. + con.stdio = .inherit; + con.step.dependOn(&con_install.step); + b.step("console", "attach a terminal to the application already on the board").dependOn(&con.step); // Ordered, for the same reason `run` is: an unordered `flash console` lets the console reset // the board out from under the writer. - const run_con = ConsoleStep.create(b, port_path, console_baud); + const run_con = b.addRunArtifact(con_exe); + run_con.addArgs(con_args); + run_con.stdio = .inherit; + run_con.step.dependOn(&con_install.step); run_con.step.dependOn(&flash.step); - b.step("interact", "flash the image, then attach an interactive terminal").dependOn(&run_con.step); + b.step("interact", "flash the image, then attach a terminal").dependOn(&run_con.step); const reset = ResetStep.create(b, port_path); b.step("reset", "reset the board and let the flashed application run").dependOn(&reset.step); @@ -413,6 +443,19 @@ pub fn build(b: *std.Build) void { }); test_step.dependOn(&b.addRunArtifact(mmio_tests).step); + // The console bridge's escape-sequence matcher. `ModeWatch` has to recognise `\x1b[?2048h` + // split across arbitrary read boundaries, and the naive reset-on-mismatch loses a sequence + // whose own ESC is the byte that broke the previous match - a bug that would show up on the + // wire as an editor that never learns the window size. Host-testable, so tested here. + const console_tests = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("tools/console.zig"), + .target = b.graph.host, + .optimize = .Debug, + }), + }); + test_step.dependOn(&b.addRunArtifact(console_tests).step); + // The radio path's host-testable parts. A wrong checksum, a wrong snprintf, or a scheduler that // loses a task is far cheaper to find here than on a board whose only output is a serial line. // @@ -1371,22 +1414,7 @@ fn readImage(step: *std.Build.Step, gpa: std.mem.Allocator, path: std.Build.Lazy /// "fixed" it, which is a genuinely confusing place to be. fn failPort(step: *std.Build.Step, port: []const u8, err: anyerror) anyerror { if (err != error.AccessDenied) return step.fail("cannot open {s}: {s}", .{ port, @errorName(err) }); - return step.fail( - \\cannot open {s}: AccessDenied - \\ - \\ {s} is owned by a group your session is not in (uucp on Arch, dialout on Debian): - \\ ls -l {s} - \\ - \\ If you are NOT in that group yet, join it and log in again: - \\ sudo usermod -aG uucp $USER - \\ - \\ If `id -nG` already lists it, this shell simply predates the change - group membership - \\ is captured at login. Either log out and back in, or take it in this shell: - \\ newgrp uucp - \\ - \\ Or run one command with the group, without touching this shell at all: - \\ sg uucp -c 'zig build ... ' - , .{ port, port, port }); + return step.fail("cannot open {s}: AccessDenied\n\n" ++ serial.access_denied_help, .{port}); } const FlashStep = struct { @@ -1522,41 +1550,6 @@ const MonitorStep = struct { } }; -/// `monitor`, but the wire runs both ways. See tools/console.zig for the size handshake, which is -/// the only part of this that is not a straight byte copy. -/// -/// Takes the same `port_lock` as every other step that opens the port, so `zig build flash console` -/// cannot have the console pull the board out of download mode mid-write. It then HOLDS that lock -/// for the whole interactive session, which is correct and worth saying out loud: the session ends -/// when the user detaches, so any other port step named on the same command line waits for the -/// human rather than racing them. -const ConsoleStep = struct { - step: std.Build.Step, - port: []const u8, - baud: serial.Baud, - - fn create(b: *std.Build, port: []const u8, baud: serial.Baud) *ConsoleStep { - const self = b.allocator.create(ConsoleStep) catch @panic("OOM"); - self.* = .{ - .step = std.Build.Step.init(.{ .id = .custom, .name = "console", .owner = b, .makeFn = make }), - .port = port, - .baud = baud, - }; - return self; - } - - fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) anyerror!void { - const self: *ConsoleStep = @fieldParentPtr("step", step); - const b = step.owner; - - port_lock.lockUncancelable(b.graph.io); - defer port_lock.unlock(b.graph.io); - - console.attach(self.port, self.baud, .{}) catch |err| - return failPort(step, self.port, err); - } -}; - /// Pulse the reset line and leave. One ioctl pair, but it is the difference between "did my app /// hang or did I forget to reset it" during development. const ResetStep = struct { |
