|
| 1 | +# 18 — Reaching a Device |
| 2 | + |
| 3 | +This document specifies how mcpp executes, writes, observes and debugs an |
| 4 | +artifact that runs somewhere other than the machine that built it, and how a |
| 5 | +project selects between an emulator and physical hardware. |
| 6 | + |
| 7 | +Related documents: [13 — Bare-Metal and Freestanding Targets](13-baremetal.md) |
| 8 | +covers the targets these actions apply to; [07 — build.mcpp](07-build-mcpp.md) |
| 9 | +is the reference for the directive protocol a board-support package speaks; |
| 10 | +[11 — Machine Output](11-machine-output.md) is the interface a debugger client |
| 11 | +or IDE uses. |
| 12 | + |
| 13 | +## Four actions, one shape |
| 14 | + |
| 15 | +An artifact that cannot run on the build machine needs something to stand in |
| 16 | +front of it. Four things are asked of such an artifact, and all four are an |
| 17 | +argv that a board knows and a tool performs: |
| 18 | + |
| 19 | +| Command | Slot | What it does | |
| 20 | +|---|---|---| |
| 21 | +| `mcpp run` | `runner` | executes the artifact | |
| 22 | +| `mcpp flash` | `flash` | writes it to the device | |
| 23 | +| `mcpp monitor` | `monitor` | observes what the device prints | |
| 24 | +| `mcpp debug` | `debug` | starts the device's debug server | |
| 25 | + |
| 26 | +Each is declared the same way, by a board-support package: |
| 27 | + |
| 28 | +```cpp |
| 29 | +mcpp::flash("probe-rs"); |
| 30 | +mcpp::flash("download"); |
| 31 | +mcpp::flash("--verify"); |
| 32 | +mcpp::flash("--chip"); |
| 33 | +mcpp::flash("STM32L475VG"); |
| 34 | +``` |
| 35 | +
|
| 36 | +or by a project, overriding what a dependency supplied: |
| 37 | +
|
| 38 | +```toml |
| 39 | +[target.thumbv7em-none-eabihf] |
| 40 | +flash = ["probe-rs", "download", "--verify", "--chip", "STM32L475VG", "{}"] |
| 41 | +``` |
| 42 | + |
| 43 | +The artifact path is appended, or substituted for `{}` when the template |
| 44 | +contains it. One token per call: argv is ordered, and a single string cannot |
| 45 | +say where its boundaries are. |
| 46 | + |
| 47 | +The program is located by mcpp rather than by the system: a declared payload's |
| 48 | +`bin/` first, then `PATH`. A tool that is nowhere is an error decided before |
| 49 | +any process starts, rather than a fallback to bare execution. |
| 50 | + |
| 51 | +## Termination is a property of the slot |
| 52 | + |
| 53 | +The four actions differ in one way the engine must act on, and no argv can |
| 54 | +express it. |
| 55 | + |
| 56 | +| Semantics | Slots | Meaning | |
| 57 | +|---|---|---| |
| 58 | +| `OneShot` | `run`, `flash` | runs to completion; the exit code is the verdict | |
| 59 | +| `LongLived` | `monitor`, `debug` | has no natural end; the operator ends it | |
| 60 | + |
| 61 | +`openocd -c "program image.elf verify reset exit"` terminates and `openocd -c |
| 62 | +"init"` does not, and the two are spelled alike up to the argument the board |
| 63 | +chose. The engine therefore reads termination from the slot, and a board cannot |
| 64 | +get it wrong by writing its argv differently. |
| 65 | + |
| 66 | +`mcpp debug` starts a **server** and stops there. The client that attaches is |
| 67 | +the user's debugger or their IDE, which learns what it needs through the |
| 68 | +machine-output protocol. mcpp does not drive the client. |
| 69 | + |
| 70 | +## Absence is reported, never substituted |
| 71 | + |
| 72 | +`mcpp run` on a hosted target with no runner executes the artifact directly, |
| 73 | +because the host can run it. There is no corresponding reading of "no flasher": |
| 74 | +nothing else writes an image to a device. An undeclared `flash`, `monitor` or |
| 75 | +`debug` is therefore an error on every target, naming the slot and printing the |
| 76 | +key to paste. |
| 77 | + |
| 78 | +Succeeding at `mcpp flash` by running the program on the build host would be |
| 79 | +the exact failure the slot exists to prevent. |
| 80 | + |
| 81 | +## An exclusive device |
| 82 | + |
| 83 | +A physical board is a mutex; an emulator is not. `mcpp test` runs test binaries |
| 84 | +on a worker pool, and two processes reaching for one probe do not fail cleanly |
| 85 | +— they interleave, and the verdict describes neither test. |
| 86 | + |
| 87 | +The board states this about itself: |
| 88 | + |
| 89 | +```cpp |
| 90 | +mcpp::runner_exclusive(); |
| 91 | +``` |
| 92 | + |
| 93 | +`mcpp test` then runs one test at a time on that target, and reports that it is |
| 94 | +doing so. A project never has to remember `-j1`. |
| 95 | + |
| 96 | +## Emulator and hardware are one package |
| 97 | + |
| 98 | +A board reached through an emulator and the same board reached through a debug |
| 99 | +probe differ in the argv of their device slots and in nothing else. The linker |
| 100 | +script, the startup code, the memory map and the exported module are the same |
| 101 | +board. Publishing two packages to vary four strings duplicates all of it and |
| 102 | +lets the copies drift. |
| 103 | + |
| 104 | +The choice is therefore a feature of one package: |
| 105 | + |
| 106 | +```toml |
| 107 | +[features] |
| 108 | +default = ["emulator"] |
| 109 | +emulator = [] |
| 110 | +hardware = [] |
| 111 | +``` |
| 112 | + |
| 113 | +```cpp |
| 114 | +int main() { |
| 115 | + if (mcpp::has_feature("hardware")) { |
| 116 | + for (auto a : {"probe-rs", "run", "--chip", "STM32L475VG"}) |
| 117 | + mcpp::runner(a); |
| 118 | + for (auto a : {"probe-rs", "download", "--verify", "--chip", "STM32L475VG"}) |
| 119 | + mcpp::flash(a); |
| 120 | + mcpp::runner_exclusive(); |
| 121 | + } else { |
| 122 | + mcpp::runner(qemu_path()); |
| 123 | + for (auto a : {"-machine", "mps2-an385", "-nographic", "-semihosting", |
| 124 | + "-no-reboot", "-kernel"}) |
| 125 | + mcpp::runner(a); |
| 126 | + } |
| 127 | + return 0; |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +The consumer selects an environment where it selects everything else: |
| 132 | + |
| 133 | +```toml |
| 134 | +[dependencies] |
| 135 | +demo-board-rt = { version = "0.1.0", features = ["hardware"] } |
| 136 | +``` |
| 137 | + |
| 138 | +A slot the chosen environment does not supply stays absent. An emulator has no |
| 139 | +debug probe, so under the emulator feature `mcpp debug` reports that none is |
| 140 | +configured rather than inventing one. |
| 141 | + |
| 142 | +This required no engine mechanism. The engine reads slots and knows nothing |
| 143 | +about emulators or probes; `mcpp::has_feature` already existed. That the |
| 144 | +question is answerable without adding anything is the layering working as |
| 145 | +specified. |
| 146 | + |
| 147 | +## Precedence and reporting |
| 148 | + |
| 149 | +Two producers exist for every slot, with ordinary precedence: what the author |
| 150 | +of the project wrote beats what a dependency supplied. The override is reported |
| 151 | +rather than applied in silence. |
| 152 | + |
| 153 | +``` |
| 154 | + note [target.thumbv7em-none-eabihf].flash overrides the flash a dependency supplied |
| 155 | +``` |
| 156 | + |
| 157 | +Exactly one dependency may supply a given slot. Link flags from two |
| 158 | +dependencies concatenate and that is correct; two flashers cannot, and |
| 159 | +appending produces an argv that is neither one's. A second provider is an error |
| 160 | +naming both packages. |
0 commit comments