|
1 | 1 | # 18 — Reaching a Device |
2 | 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. |
| 3 | +This document specifies how mcpp executes an artifact that runs somewhere other |
| 4 | +than the machine that built it, how a package supplies additional ways of |
| 5 | +reaching it, and how a project selects between an emulator and physical |
| 6 | +hardware. |
6 | 7 |
|
7 | 8 | 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. |
| 9 | +covers the targets this most often applies to; [07 — build.mcpp](07-build-mcpp.md) |
| 10 | +is the reference for the directive protocol a package speaks; [11 — Machine |
| 11 | +Output](11-machine-output.md) is the interface a debugger client or IDE uses. |
12 | 12 |
|
13 | | -## Four actions, one shape |
| 13 | +## One command, and named exceptions |
14 | 14 |
|
15 | 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: |
| 16 | +front of it. That thing is a **runner**: an argv the package supplies and a tool |
| 17 | +performs, with the artifact appended or substituted for `{}`. |
18 | 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 | |
| 19 | +```bash |
| 20 | +mcpp run # the default runner |
| 21 | +mcpp run --runner flash # a named one |
| 22 | +mcpp run --list-runners # what this project supplies |
| 23 | +``` |
25 | 24 |
|
26 | | -Each is declared the same way, by a board-support package: |
| 25 | +`mcpp run` is the whole of the common case, including on real hardware. On a |
| 26 | +device, running a program means writing it, resetting, attaching to its output |
| 27 | +and reading its exit status — which is one command (`probe-rs run`, `qemu-system-* |
| 28 | +-kernel`), not several. A board therefore supplies that as its **default** |
| 29 | +runner, and the command a developer types does not change when they move from an |
| 30 | +emulator to a board. |
27 | 31 |
|
28 | | -```cpp |
29 | | -mcpp::flash("probe-rs"); |
30 | | -mcpp::flash("download"); |
31 | | -mcpp::flash("--verify"); |
32 | | -mcpp::flash("--chip"); |
33 | | -mcpp::flash("STM32L475VG"); |
34 | | -``` |
| 32 | +Named runners exist for what remains: writing an image without running it, |
| 33 | +observing a console, starting a debug server, erasing a part, deploying without |
| 34 | +starting. |
35 | 35 |
|
36 | | -or by a project, overriding what a dependency supplied: |
| 36 | +⚠️ **The engine knows no runner names.** `flash`, `serve`, `deploy`, `submit` |
| 37 | +and `logcat` are equally unknown to it: it knows only that a package may supply |
| 38 | +named runners, and performs the argv it finds. A fixed set of names in the |
| 39 | +engine would decide, in the engine, which domains are expressible. |
37 | 40 |
|
38 | | -```toml |
39 | | -[target.thumbv7em-none-eabihf] |
40 | | -flash = ["probe-rs", "download", "--verify", "--chip", "STM32L475VG", "{}"] |
41 | | -``` |
| 41 | +## What a package supplies |
42 | 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. |
| 43 | +```cpp |
| 44 | +mcpp::runner("qemu-system-arm"); // the default: argv token by token |
| 45 | +mcpp::runner("-machine"); mcpp::runner("mps2-an385"); … |
46 | 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. |
| 47 | +mcpp::runner("flash", "probe-rs"); // a named runner |
| 48 | +mcpp::runner("flash", "download"); … |
50 | 49 |
|
51 | | -## Termination is a property of the slot |
| 50 | +mcpp::runner_longlived("monitor"); // no natural end |
| 51 | +mcpp::run_exclusive(); // this target's runs cannot overlap |
| 52 | +``` |
52 | 53 |
|
53 | | -The four actions differ in one way the engine must act on, and no argv can |
54 | | -express it. |
| 54 | +⭐ **Name the program, not its path.** mcpp locates it: the `bin/` of a payload |
| 55 | +this package declared under `[xlings] deps` first, then `PATH`. Writing an |
| 56 | +absolute path computed from `mcpp::xpkg_dir` is unnecessary, and it introduces a |
| 57 | +failure mode — a declaration is not an install, so the lookup can return empty |
| 58 | +and leave no runner configured with nothing said about why. Naming the program |
| 59 | +lets mcpp report exactly which directories it searched. |
55 | 60 |
|
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 | |
| 61 | +## What a project overrides |
60 | 62 |
|
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. |
| 63 | +```toml |
| 64 | +[target.thumbv7em-none-eabihf] |
| 65 | +runner = ["qemu-system-arm", "-machine", "mps2-an385", "-kernel"] |
| 66 | +
|
| 67 | +[target.thumbv7em-none-eabihf.runners] |
| 68 | +flash = ["probe-rs", "download", "--verify", "--chip", "STM32L475VG", "{}"] |
| 69 | +monitor = ["probe-rs", "attach", "--chip", "STM32L475VG"] |
| 70 | +``` |
65 | 71 |
|
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. |
| 72 | +Precedence is the ordinary one: what the author of the project wrote beats what |
| 73 | +a dependency supplied, and the override is reported rather than applied in |
| 74 | +silence. Exactly one dependency may supply a given name; a second is an error |
| 75 | +naming both packages. |
69 | 76 |
|
70 | | -## Absence is reported, never substituted |
| 77 | +## Termination is declared, not inferred |
71 | 78 |
|
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. |
| 79 | +| | Meaning | |
| 80 | +|---|---| |
| 81 | +| default | runs to completion; the exit code is the verdict | |
| 82 | +| `runner_longlived(name)` | has no natural end; the operator ends it | |
77 | 83 |
|
78 | | -Succeeding at `mcpp flash` by running the program on the build host would be |
79 | | -the exact failure the slot exists to prevent. |
| 84 | +`openocd -c "program image.elf verify reset exit"` terminates and `openocd -c |
| 85 | +"init"` does not, and the two are spelled alike up to the argument the package |
| 86 | +chose. No argv can express which is which, and the engine has no list of names |
| 87 | +to infer it from — so the package states it. |
80 | 88 |
|
81 | | -## An exclusive device |
| 89 | +`mcpp run --runner debug` starts a **server** and stops there. The client that |
| 90 | +attaches is the user's debugger or IDE, which learns what it needs through the |
| 91 | +machine-output protocol. |
82 | 92 |
|
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. |
| 93 | +## Runs that cannot overlap |
86 | 94 |
|
87 | | -The board states this about itself: |
| 95 | +`mcpp test` runs test binaries on a worker pool. One board on one probe, one |
| 96 | +GPU, one serial port, or a tool with a single-seat licence admits one user at a |
| 97 | +time, and two workers reaching for it do not fail cleanly — they interleave, and |
| 98 | +the verdict describes neither test. |
88 | 99 |
|
89 | | -```cpp |
90 | | -mcpp::runner_exclusive(); |
91 | | -``` |
| 100 | +The package states this about itself with `mcpp::run_exclusive()`, and `mcpp |
| 101 | +test` then serialises. A project never has to remember `-j1`. |
92 | 102 |
|
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`. |
| 103 | +Named for the property rather than for the hardware: nothing here is about |
| 104 | +devices. |
95 | 105 |
|
96 | 106 | ## Emulator and hardware are one package |
97 | 107 |
|
98 | 108 | 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 |
| 109 | +probe differ in the argv of their runners and in nothing else. The linker |
100 | 110 | 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 |
| 111 | +board. Publishing two packages to vary a few strings duplicates all of it and |
102 | 112 | lets the copies drift. |
103 | 113 |
|
104 | 114 | The choice is therefore a feature of one package: |
105 | 115 |
|
106 | 116 | ```toml |
107 | 117 | [features] |
108 | 118 | default = ["emulator"] |
109 | | -emulator = [] |
110 | | -hardware = [] |
| 119 | +emulator = {} |
| 120 | +hardware = {} |
111 | 121 | ``` |
112 | 122 |
|
113 | 123 | ```cpp |
114 | 124 | int main() { |
115 | 125 | if (mcpp::has_feature("hardware")) { |
116 | 126 | 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(); |
| 127 | + mcpp::runner(a); // the DEFAULT moves |
| 128 | + for (auto a : {"probe-rs", "gdb", "--chip", "STM32L475VG"}) |
| 129 | + mcpp::runner("debug", a); |
| 130 | + mcpp::runner_longlived("debug"); |
| 131 | + mcpp::run_exclusive(); |
121 | 132 | } else { |
122 | | - mcpp::runner(qemu_path()); |
123 | | - for (auto a : {"-machine", "mps2-an385", "-nographic", "-semihosting", |
124 | | - "-no-reboot", "-kernel"}) |
| 133 | + for (auto a : {"qemu-system-arm", "-machine", "mps2-an385", "-nographic", |
| 134 | + "-semihosting", "-no-reboot", "-kernel"}) |
125 | 135 | mcpp::runner(a); |
126 | 136 | } |
127 | 137 | return 0; |
128 | 138 | } |
129 | 139 | ``` |
130 | 140 |
|
131 | | -The consumer selects an environment where it selects everything else: |
132 | | - |
133 | 141 | ```toml |
134 | 142 | [dependencies] |
135 | | -demo-board-rt = { version = "0.1.0", features = ["hardware"] } |
| 143 | +cortex-m-rt = { version = "0.1.0", features = ["hardware"] } |
136 | 144 | ``` |
137 | 145 |
|
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. |
| 146 | +The consumer's command does not change. A runner the chosen environment does not |
| 147 | +supply stays absent: an emulator has no debug probe, so under the emulator |
| 148 | +feature `mcpp run --runner debug` reports that no such runner exists and lists |
| 149 | +the ones that do. |
141 | 150 |
|
142 | | -This required no engine mechanism. The engine reads slots and knows nothing |
| 151 | +⭐ This required no engine mechanism. The engine reads runners and knows nothing |
143 | 152 | about emulators or probes; `mcpp::has_feature` already existed. That the |
144 | 153 | question is answerable without adding anything is the layering working as |
145 | 154 | 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