Joel Winarske 088941bdd8 feat(cross): multi-platform manifests via cross.targets + --target
Switch between boards (rpi4/rpi5/rpi-zero-2w/radxa-zero3, …) from a single
manifest. Shared config lives at the cross: level; each platform under
cross.targets overrides only what differs (image_url, cpu_flags). Select one
with --target <name>; --list-targets prints the defined platforms. A manifest
with no targets behaves exactly as before (one implicit target).

Working dirs are content-hash keyed via a two-tier scheme so same-triple boards
no longer collide and identical sysroots are shared:
- sysrootKey (excludes cpu_flags) names the toolchain+sysroot dir, so cpu-only
  variants on one image (rpi4/rpi5/zero-2w) share a single extraction.
- buildKey (full config) names the per-board build dir; the emitted cmake
  toolchain file is cpu-named so variants coexist in the shared sysroot dir.
A different image (radxa) gets its own sysroot.

cross-build/cross/yocto-sdk dirs and --clean are updated to the keyed names.
Adds examples/cross/raspberry-pi-family.emb.yaml and README docs.

Signed-off-by: Joel Winarske <joel.winarske@gmail.com>
2026-06-16 16:19:41 -07:00
2024-08-23 11:47:01 -07:00
2024-08-23 11:47:01 -07:00
2024-08-23 11:47:01 -07:00

emb — Flutter Embedder CLI

style: very good analysis License: Apache-2.0

emb provisions a Flutter embedded-Linux development workspace and builds deployable app bundles for embedders such as ivi-homescreen. It's a Dart port of meta-flutter/workspace-automation (flutter_workspace.py + create_aot.py).

The whole flow is a handful of commands:

deps → repos → Flutter SDK → engine → AOT → ivi-homescreen bundle
  • Host dependency install in one transaction (PackageKit on Linux), with WhatProvides resolution so pkg-config, libjpeg-devel, etc. just work.
  • Prebuilt Flutter engine fetched from meta-flutter/flutter-engine releases (auto, keyed by the SDK's engine commit).
  • Cross-compile AOT for arm64 / riscv64 from an x86_64 host using the engine's simulator gen_snapshot — no qemu (the artifact is self-contained).
  • Self-describing packages: a package declares its build in a manifest and emb build <dir> does the rest.

Requirements

  • Dart SDK ≥ 3.10.1 (to run/build the emb tool itself).
  • Linux for host dependency install (PackageKit — dnf/apt/zypper). The macOS (Homebrew) and Windows (WinGet) backends are stubbed in this build; all other commands are cross-platform.
  • git, tar, curl on PATH.
  • Cross-compiling to a device arch is supported from an x86_64 host.

Install

# From the package root:
dart pub get
dart pub global activate --source=path .   # puts `emb` on PATH

emb --help

Make sure the pub-global bin dir ($PUB_CACHE/bin, e.g. ~/.pub-cache/bin) is on PATH. You can also run without activating:

dart run bin/emb.dart <command>     # from the package root

The Linux backend loads packagekit_dart's native bridge (libpackagekit_nc.so). emb locates it automatically — from the package's own build, or from the package:hooks build-hook output under .dart_tool/. Override with PK_NC_LIB=/path/to/libpackagekit_nc.so.


Quick start

# 0. Check host detection + package backend
emb doctor

# 1. Provision the workspace (host deps + repos + Flutter SDK + engine)
emb setup --config ../configs --yes

# 2. Load the environment it wrote (Flutter/Dart on PATH, FLUTTER_WORKSPACE, …)
. ./setup_env.sh

# 3. Build an ivi-homescreen bundle for a Raspberry Pi (arm64), release AOT
emb bundle --app-path ./app/my_app --arch arm64 --build

# 4. Run it on the target
#    ivi-homescreen --b=<workspace>/bundle/my_app-release-arm64

emb setup runs every phase; you can also run them individually (emb deps, emb sync, emb flutter, emb engine).


Workspace & path resolution

Every command that touches the workspace resolves its root in this order:

  1. --workspace <dir> (explicit flag), else
  2. the $FLUTTER_WORKSPACE environment variable, else
  3. the current working directory.
<workspace>/                              # the resolved root
  app/                                    # cloned source repositories
  flutter/                                # Flutter SDK
  bundle/                                 # built bundles (default output)
  setup_env.sh                            # generated environment script
  .config/flutter_workspace/
    flutter-engine/<commit>/...           # downloaded + extracted engine SDKs
    flutter-engine/bundle-<mode>-<arch>/  # staged engine halves (icudtl + .so)

Command reference

emb <command> [arguments]

Global options

Option Description
-h, --help Print usage. Works at the top level and per command (emb <cmd> --help).
-v, --version Print the CLI version.
--[no-]verbose Noisy logging, including every shell command executed.

--workspace (-w), shown on most commands, follows the resolution order above. --mode/--arch defaults and value sets differ per command — see each entry.


emb doctor

Report host detection (os / arch / distro) and package-manager backend availability. No options. Exit code is non-zero if the backend is unavailable.

emb doctor

emb setup

One-shot provision: deps → repos → Flutter SDK → engine, then writes setup_env.sh. Each phase is individually skippable.

Option Default Description
-c, --config <dir> configs Legacy JSON config directory.
-p, --packages <dir> Directory to discover self-describing emb manifests.
--enable <id> Force-load the config with this id (overrides load: false). Repeatable; unmatched ids ignored.
--disable <id> Skip the config with this id (overrides load: true). Repeatable; unmatched ids ignored.
-w, --workspace <dir> resolution order Workspace root.
--flutter-version <ref> globals.json flutter_version Flutter version/tag/branch.
--arch <arch> host arch Engine arch to prefetch.
-m, --mode <mode> release Engine runtime modes to prefetch (repeatable): release, profile, debug.
-y, --yes off Skip the deps confirmation prompt (CI).
--skip-deps off Skip host dependency install.
--skip-sync off Skip repository sync.
--skip-flutter off Skip Flutter SDK install.
--skip-engine off Skip engine artifact fetch.
emb setup --config ../configs --yes
emb setup --config ../configs --skip-deps --skip-sync   # SDK + engine only
emb setup --config ../configs --enable weston --disable agl-compositor

Config selection (applies to setup, deps, and sync): legacy --config components apply only when their load flag is logically true. --enable <id> / --disable <id> override that per component, matched by id; ids matching nothing are ignored, and load order is preserved.


emb deps

Coalesce host dependencies across all selected manifests, filter to what's missing on this host, and install the union in one transaction.

Option Default Description
-c, --config <dir> configs Legacy JSON config directory. Repeatable.
-p, --packages <dir> Directory to discover self-describing emb manifests. Repeatable.
--enable <id> Force-load the config with this id (overrides load: false). Repeatable; unmatched ids ignored.
--disable <id> Skip the config with this id (overrides load: true). Repeatable; unmatched ids ignored.
--dry-run off Resolve and print the install plan without changing the system.
-y, --yes off Skip the confirmation prompt (CI).
emb deps --config ../configs --dry-run      # plan only
emb deps --config ../configs --yes          # install in one transaction

emb sync

Clone/update source repositories into <workspace>/app (bounded concurrency).

Option Default Description
-c, --config <dir> configs Legacy JSON config directory. Repeatable.
-p, --packages <dir> Directory to discover self-describing emb manifests.
--enable <id> Force-load the config with this id (overrides load: false). Repeatable; unmatched ids ignored.
--disable <id> Skip the config with this id (overrides load: true). Repeatable; unmatched ids ignored.
--repos <file> A JSON file containing a bare array of repo entries. Repeatable.
-w, --workspace <dir> resolution order Workspace root.
-j, --concurrency <n> 4 Maximum concurrent git operations.
emb sync --config ../configs -j 8

emb flutter

Install the Flutter SDK into <workspace>/flutter.

Option Default Description
-w, --workspace <dir> resolution order Workspace root.
--flutter-version <ref> globals.json flutter_version Version/tag/branch to check out.
-c, --config <dir> configs Directory to read globals.json from.
--configure off Run flutter config (desktop + custom devices) and flutter doctor after install.
emb flutter --flutter-version 3.44.2 --configure

emb engine

Fetch prebuilt Flutter engine artifacts (auto fetch-else-build). Modes you don't fetch here are auto-fetched on demand by emb bundle/emb build.

Option Default Description
-w, --workspace <dir> resolution order Workspace root.
--commit <sha> <workspace>/flutter/bin/internal/engine.version Engine commit to fetch.
--arch <arch> host arch Engine arch token (see Architectures).
-m, --mode <mode> release Runtime modes to fetch (repeatable): release, profile, debug.
--clean off Re-stage bundles even when already present.
--check off Only check prebuilt availability; do not download.
emb engine --arch arm64 --mode release --mode profile
emb engine --arch riscv64 --check       # is a prebuilt available?

emb aot

Build the AOT image (libapp.so) for a Flutter app — the AOT primitive used by emb bundle --build. Debug is not AOT, so only release/profile apply.

Option Default Description
-a, --app-path <dir> mandatory Path to the Flutter application to build.
-w, --workspace <dir> resolution order Workspace root.
-m, --mode <mode> release Runtime modes to build (repeatable): release, profile.
--arch <arch> host arch Target arch for gen_snapshot (e.g. arm64 for a Pi).
--gen-snapshot <path> auto-resolved Explicit gen_snapshot path (overrides resolution).
--glibc-sysroot <dir> artifact's bundled clang_x64/lib64 Directory with ld-linux + libc to run gen_snapshot under.
emb aot --app-path ./app/my_app --arch arm64 --mode release --mode profile

emb bundle

Assemble an ivi-homescreen bundle from app + engine artifacts. With --build it runs emb aot first; the engine SDK for the (mode, arch) is fetched implicitly if not already cached.

Option Default Description
-a, --app-path <dir> mandatory Path to the Flutter application.
-w, --workspace <dir> resolution order Workspace root.
-m, --mode <mode> release Single mode: debug (JIT, no AOT), profile, or release (AOT).
--arch <arch> host arch Target arch (e.g. arm64).
-o, --output <dir> (alias --out) <workspace>/bundle/<app>-<mode>-<arch> Output bundle directory — any path.
--build off Run emb aot first to (re)build flutter_assets + libapp.so.
emb bundle --app-path ./app/my_app --arch arm64   --build                # release
emb bundle --app-path ./app/my_app --arch arm64   --build --mode debug   # JIT
emb bundle --app-path ./app/my_app --arch riscv64 --build --mode profile
emb bundle --app-path ./app/my_app --build                               # host
emb bundle --app-path ./app/my_app --arch arm64 --output /tmp/out        # custom path

emb build

Build a self-describing package — a directory with an emb.yaml (or an emb: key in pubspec.yaml) — into bundles, using the manifest's build: matrix. The package directory is a positional argument (mandatory; omitting it prints a usage error).

emb build <package-dir> [options]
Option Default Description
<package-dir> mandatory (positional) Directory containing emb.yaml or a pubspec.yaml with an emb: key.
-w, --workspace <dir> resolution order Workspace root.
--arch <arch> manifest's archs Override target arch(es). Repeatable.
-m, --mode <mode> manifest's modes Override mode(s): debug, profile, release. Repeatable.
--no-build off Assemble from existing artifacts; skip compiling.
emb build ./app/my_app                              # full manifest matrix
emb build ./app/my_app --arch arm64 --mode release  # override the matrix
emb build ./app/my_app --no-build                   # assemble only

Manifest (emb.yaml)

id: my_app
type: app
build:
  app_path: .              # Flutter app dir, relative to this manifest (default ".")
  archs: [arm64, x86_64]   # target architectures (default: host)
  modes: [release, debug]  # default: [release]
  output: bundles          # optional output dir, relative to the workspace
deps:                      # optional host packages, by OS / distro
  linux:
    fedora: [pkg-config, freetype-devel]
    ubuntu: [pkg-config, libfreetype-dev]
  macos: [pkg-config, freetype]
  windows: [Kitware.CMake]

emb cross

Cross-compile a native embedder (e.g. ivi-homescreen) for an arm64 / riscv64 target from an x86_64 host, driven by the manifest's cross: block. This is the C/C++ toolchain + sysroot path — distinct from the Dart AOT cross used by emb build / emb bundle. Three providers: arm-gnu (a downloaded ARM GNU toolchain plus a sysroot unpacked from a distro image or rsync'd from a device), yocto-recipe (a located OE recipe-sysroot), and yocto-sdk (a populate_sdk install). The input is a positional package dir (with emb.yaml) or an explicit manifest file.

emb cross <package-dir|manifest.yaml> [options]
Option Default Description
<package-dir|manifest> mandatory (positional) Dir with emb.yaml, or a manifest file.
-w, --workspace <dir> resolution order Workspace root.
-t, --target <name> Select a platform from cross.targets (e.g. rpi5, radxa-zero3); its fields override the shared cross: block. Required when the manifest defines cross.targets.
--list-targets off List the platforms defined under cross.targets, then exit.
--dry-run off Report the resolution plan (provider, toolchain, sysroot, preflight, augment, backends) with no download / mount / ssh.
--prepare off After resolving, build the augment libraries into the overlay.
--build off Configure + build the embedder under the resolved profile, one build per cross.backends entry.
--backend <name> all Build only the named cross.backends entries. Repeatable.
--deb off With --build: package each backend binary into a root-free .deb (Depends auto-derived from the binary's needed libraries).
--clean off Remove this target's build + overlay dirs (keeps the toolchain + sysroot), then exit.
--clean-all off Also remove the downloaded / extracted toolchain + sysroot and the apt / deb caches, then exit.
emb cross ./app/ivi-homescreen --dry-run      # plan only, no side effects
emb cross ./app/ivi-homescreen --build        # toolchain + sysroot + build
emb cross ./app/ivi-homescreen --build --deb  # ...and package a .deb
emb cross ./app/ivi-homescreen --clean        # drop build dirs (keep toolchain)
emb cross ./app/ivi-homescreen --clean-all    # drop everything for this target

Everything is root-free: the sysroot is extracted with debugfs / dpkg-deb, and -dev packages are resolved against the image's own apt sources — no apt, no chroot, no sudo. Validated end-to-end on Raspberry Pi (arm-gnu, raspios bookworm): --build --deb produces an aarch64 ELF and an installable .deb. See examples/cross/ for one manifest per board (pi5 is the validated end-to-end example) and the full schema.

Manifest (cross: block)

cross:
  provider: arm-gnu               # arm-gnu | yocto-recipe | yocto-sdk
  toolchain_version: 12.3.rel1    # pinned ARM GNU release (or version_policy: derive)
  image_url: https://.../raspios-bookworm-arm64-lite.img.xz
  cpu_flags: [-mcpu=cortex-a76]   # pi5; pi4=cortex-a72, pi-zero-2=cortex-a53
  sysroot:
    partition: 2                  # rootfs partition in the image (default 2)
    dev_packages: [libdrm-dev, libegl-dev, libgbm-dev, libinput-dev]
  augment:                        # libs built from source when the sysroot is too old
    - { pkg: libdisplay-info, min: "0.2.0", url: https://.../libdisplay-info-0.2.0.tar.gz, build: meson, static: true }
  defines:                        # -D<name>=<value> applied to every build
    CMAKE_INSTALL_PREFIX: /usr
  cmake_args: [-Wno-dev]          # raw cmake configure flags (cmake only)
  backends:                       # one build per entry; -D<key>=<value> each
    drm-kms-egl: { BUILD_BACKEND_DRM_KMS_EGL: 'ON', DISABLE_PLUGINS: 'ON' }
  package:                        # optional, consumed by --deb
    name: ivi-homescreen
    version: 1.0.0
    bin: shell/homescreen         # binary, relative to each backend build dir
    install_dir: /usr/bin

Multiple platforms in one manifest (cross.targets)

To target several boards from a single manifest, put the shared config at the cross: level and a per-board override under cross.targets, then pick one with --target:

cross:
  provider: arm-gnu               # shared by every target
  toolchain_version: 12.3.rel1
  sysroot: { dev_packages: [libdrm-dev, libegl-dev, libgbm-dev, libinput-dev] }
  backends: { drm-kms-egl: { BUILD_BACKEND_DRM_KMS_EGL: 'ON' } }
  targets:                        # per-board overrides
    rpi5:        { image_url: …raspios…, cpu_flags: [-mcpu=cortex-a76] }
    rpi4:        { image_url: …raspios…, cpu_flags: [-mcpu=cortex-a72] }
    rpi-zero-2w: { image_url: …raspios…, cpu_flags: [-mcpu=cortex-a53] }
    radxa-zero3: { image_url: …radxa…,   cpu_flags: [-mcpu=cortex-a55] }
emb cross . --list-targets
emb cross . --target rpi5 --build --deb
emb cross . --target radxa-zero3 --build

A target's fields shallow-merge over the shared block (a top-level image_url folds into sysroot). Working dirs are content-hash-keyed, so boards that share a sysroot (rpi4/rpi5/zero-2w — same image, only -mcpu differs) extract it once, while a different image (radxa) gets its own. A manifest with no cross.targets behaves exactly as before (one implicit target).


emb env

Write setup_env.sh (PATH for Flutter/Dart, FLUTTER_WORKSPACE, PUB_CACHE, XDG_CONFIG_HOME, the engine version, …).

Option Default Description
-w, --workspace <dir> resolution order Workspace root.
-o, --output <path> <workspace>/setup_env.sh Output file path.
--print off Print to stdout instead of writing a file.
emb env                 # write <workspace>/setup_env.sh
emb env --print         # preview on stdout

emb update

Update the CLI itself (via pub). No options.

emb update

Modes

--mode How it builds Bundle contents Valid in
debug JIT — flutter build bundle --debug kernel_blob.bin, no libapp.so bundle, build, engine, setup
profile AOT (gen_snapshot) libapp.so (+ profile engine) all
release AOT (gen_snapshot) libapp.so (+ release engine) all

emb aot only accepts release/profile (debug isn't AOT). Where a command takes multiple modes (aot, build, engine, setup), repeat the flag: --mode release --mode profile. emb bundle takes a single mode.

A bundle is the directory ivi-homescreen consumes:

<bundle>/
  data/flutter_assets/        # app code + assets
  data/icudtl.dat             # from the engine
  lib/libflutter_engine.so    # from the engine (per mode)
  lib/libapp.so               # AOT image (profile/release only)

Architectures & cross-compiling

--arch accepts machine names or Flutter tokens; they normalize to one of four engine arch tokens used in paths and bundle names:

You pass Normalized token
x64, x86_64, amd64 x86_64
arm64, aarch64 arm64
arm, armv7, armv7hf, armhf armv7hf
riscv64 riscv64

Cross-compile is x86_64 host → target arch. The meta-flutter engine SDK ships a host-x86_64 simulator gen_snapshot that emits target code and carries its own loader/libc in clang_x64/lib64, so it runs on any host glibc — no qemu required. emb selects and runs it automatically; --gen-snapshot / --glibc-sysroot override if needed. Building on an arm64 host targets arm64 natively.


Development

dart pub get
dart analyze
dart test

The macOS/Windows package backends live in lib/src/pkg/_platform/ and are excluded from the default (Linux) build — see that folder's README.md. Dart has no OS-conditional dependencies, so they're wired in only on per-OS builds that add brew_dart / winget_dart.

S
Description
No description provided
Readme Apache-2.0 640 KiB
Languages
Dart 98.5%
Python 1.4%
Shell 0.1%