docs(cross): document + demonstrate app-layer extends

Add a README subsection ("Layered manifests: extends, board → project → app")
covering both extends forms (<board> and <dir>#<target>) and the merge rules,
with project and app manifest snippets.

Add examples/cross/app-extends.emb.yaml: an app target that extends the rpi
family file's rpi5-bookworm target (which extends the board) and overlays an
app-specific dev package — a real three-layer chain. A cross_command test
--dry-runs it so it stays valid.

Signed-off-by: Joel Winarske <joel.winarske@gmail.com>
This commit is contained in:
Joel Winarske
2026-06-20 07:13:21 -07:00
parent 10b9eaf902
commit 8417e3a0e8
4 changed files with 89 additions and 0 deletions
+1
View File
@@ -38,6 +38,7 @@
"libglib",
"libgstreamer",
"libinput",
"libnl",
"libpipewire",
"libsecret",
"libsystemd",
+48
View File
@@ -589,6 +589,54 @@ emb cross . --target local # …the same, explicit (or --target host)
emb cross . --target rpi5 --build
```
#### Layered manifests: `extends` (board → project → app)
emb ships a **board library** — hardware/OS definitions for known boards under
its own `boards/` directory (e.g. `rpi5-bookworm`: triple, toolchain version,
sysroot image, partition, cpu tuning, the base GUI stack). A manifest pulls one
in with `cross.extends` so a project never re-spells hardware facts:
```yaml
# ivi-homescreen/.emb/raspberry-pi.emb.yaml — the PROJECT layer
cross:
targets:
rpi5-bookworm:
extends: rpi5-bookworm # ← emb board (hardware/OS)
backends: { drm-kms-egl: { BUILD_BACKEND_DRM_KMS_EGL: 'ON' } }
augment: [ { pkg: libdisplay-info, min: "0.2.0", build: meson } ]
sysroot: { dev_packages: [ libgstreamer1.0-dev ] } # added to the board's
```
`extends` takes two forms:
| Form | Resolves to |
|---|---|
| `<board>` | a target in emb's board library (the **hardware** layer) |
| `<dir>#<target>` | a target in another emb project at `<dir>` (the **project** layer) |
The second form lets an **app** build on a project (e.g. ivi-homescreen) and add
only what is app-specific — typically **plugin configuration**, which depends on
the plugins the app ships, not on the embedder or the board:
```yaml
# my-flutter-app/.emb/app.emb.yaml — the APP layer
# (ivi-homescreen checked out alongside the app)
cross:
targets:
rpi5-bookworm:
extends: '../ivi-homescreen#rpi5-bookworm' # ← project target (→ board)
defines: { DISABLE_PLUGINS: 'OFF', PLUGINS_DIR: '../ivi-homescreen-plugins/plugins' }
sysroot: { dev_packages: [ libnl-3-dev ] }
```
The chain collapses **app ⊕ project ⊕ board** with the same merge rules at every
layer: nested maps deep-merge; `cpu_flags` and `backends` **replace** (each is a
complete statement); `sysroot.dev_packages` **union** (each layer adds to the
stack below it). `<dir>` is resolved relative to the extending manifest's project
root (the `.emb/` parent, else the file's directory); cross-project reference
cycles are rejected. The board library location can be overridden with
`EMB_BOARDS_DIR` (it otherwise ships with emb).
#### Reproducible builds (`emb.lock`)
A cross resolve pins what it actually used to **`emb.lock`** at the project root,
+25
View File
@@ -0,0 +1,25 @@
# App layer demo — a Flutter app that builds on a project (board → project → app).
#
# `extends: <ref>#<target>` pulls in raspberry-pi-family.emb.yaml's rpi5-bookworm
# target — which itself `extends` the rpi5-bookworm board — then overlays only
# what is app-specific. Here that's one extra plugin -dev package, merged onto
# the project + board sysroot stack; a real app would typically also set its own
# plugin config (DISABLE_PLUGINS / PLUGINS_DIR). Hardware (toolchain, image, cpu)
# comes from the board; backends/augment from the project.
#
# emb cross app-extends.emb.yaml --target rpi5-bookworm --dry-run
#
# In a real layout the app and the project live in separate repos checked out
# side by side, so the ref is a directory, e.g.:
# extends: '../ivi-homescreen#rpi5-bookworm'
id: my-flutter-app
type: app
supported_archs: [arm64]
supported_host_types: [ubuntu, fedora]
cross:
targets:
rpi5-bookworm:
extends: 'raspberry-pi-family.emb.yaml#rpi5-bookworm'
sysroot:
dev_packages: [libnl-3-dev] # an app plugin dep, added to the stack
+15
View File
@@ -326,6 +326,21 @@ void main() {
}
});
test(
'--dry-run plans the app-extends example (app → project → board)',
() async {
final app = p.join('examples', 'cross', 'app-extends.emb.yaml');
final code = await run([
'cross',
'--dry-run',
'--target',
'rpi5-bookworm',
app,
]);
expect(code, ExitCode.success.code);
},
);
// Every shipped example must parse -> dispatch -> plan with no side effects:
// this validates all of the listed use cases hermetically.
test('--dry-run plans every example manifest', () async {