fix: don't auto-install shell completion on every run
emb — Flutter Embedder CLI
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
WhatProvidesresolution sopkg-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/riscv64from anx86_64host using the engine's simulatorgen_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
embtool 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,curlonPATH.- 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).emblocates it automatically — from the package's own build, or from thepackage:hooksbuild-hook output under.dart_tool/. Override withPK_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:
--workspace <dir>(explicit flag), else- the
$FLUTTER_WORKSPACEenvironment variable, else - 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, andsync): legacy--configcomponents apply only when theirloadflag is logically true.--enable <id>/--disable <id>override that per component, matched byid; 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> |
local if cross.targets defined |
Select a platform from cross.targets (e.g. rpi5, radxa-zero3), or local/host for a native build on this machine. When the manifest defines cross.targets, omitting --target defaults to local. |
--list-targets |
off | List the platforms defined under cross.targets (plus the built-in local), 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).
There's always a built-in local target (alias host): a native build on
this machine — no cross toolchain or sysroot, host compiler + system libraries
(install host dev deps via emb deps). It's the default when a manifest
defines cross.targets and you don't pass --target, so emb cross . --build
builds for the dev box while --target rpi5 cross-builds.
emb cross . --build # native local build (default with cross.targets)
emb cross . --target local # …the same, explicit (or --target host)
emb cross . --target rpi5 --build
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.