Building an app

Building an app

The whole build system is one Makefile that includes the SDK recipe in
tools/app.mk.

Project layout

myapp/
  Makefile       # include of tools/app.mk
  main.cpp
  myapp_ICON.bin # optional 40x40 icon, see Icon below
  src/i18n/      # optional app translation catalog
SDK_ROOT ?= $(abspath ../..)      # path to this repo
include $(SDK_ROOT)/tools/app.mk

Everything is overridable on the command line or in the Makefile:
APP_SRCS (default main.cpp), APP_NAME (default the directory basename),
APP_BUILD_DIR (default build), XTENSA_CXX (default: probed toolchain).

Toolchain

Requirement Value
Compiler espressif xtensa-esp32s3-elf-g++
Output little-endian (required by the loader)

The PlatformIO toolchain-xtensa-esp32s3 package emits big-endian ELFs and
is unusable for apps; if it is the only compiler found, app.mk fails the
build with a clear error. Install the espressif toolchain so it lands at one
of the probed paths (newest first):

~/.espressif/tools/xtensa-esp-elf/esp-15.2.0_20251204/xtensa-esp-elf/bin
~/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20251107/xtensa-esp-elf/bin
~/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin

esp-15.2.0_20251204 is the one SDK CI pins (URL and SHA256). It is the
easiest known-good install:

VER=esp-15.2.0_20251204
SHA=3d50f5cd5f173acfd524e07c1cd69bc99585731a415ca2e5bce879997fe602b8
URL="https://github.com/espressif/crosstool-NG/releases/download/$VER/xtensa-esp-elf-15.2.0_20251204-x86_64-linux-gnu.tar.xz"
mkdir -p "$HOME/.espressif/tools/xtensa-esp-elf/$VER"
curl -fsSL -o /tmp/xtensa-esp-elf.tar.xz "$URL"
echo "$SHA  /tmp/xtensa-esp-elf.tar.xz" | sha256sum -c -
tar -xJf /tmp/xtensa-esp-elf.tar.xz -C "$HOME/.espressif/tools/xtensa-esp-elf/$VER"

The example builds with any probed version. The host OS pins its own
toolchain independently of the SDK.

Targets

Target Result
make Builds build/<name>.app.elf, stripped, endianness-asserted
make check Prints the entry point and every undefined symbol (the app’s host dependency list). Informational; the enforcing gate is pm check
make pack Builds build/<name>.tar with <name>.app.elf, <name>_ICON.bin and app.properties when present (see publish.md)
make clean Removes build/

The endianness assertion runs after every link. A big-endian artifact gets
deleted and the build fails.

Entry point

extern "C" int main(int argc, char **argv), renamed to app_main via
-Dmain=app_main -e app_main. See app-abi.md for the full
contract. Everything the app calls resolves from the host at load time, so
keep the undefined-symbol list (from make check) small and intentional; see
symbols.md.

Compiler flags

The SDK builds with -Wall -Wextra, exceptions and RTTI disabled, and hidden
visibility. Keep the code warning-free.

app.mk injects PM_SDK_VERSION plus PM_SDK_VERSION_MAJOR/MINOR/PATCH from
the repo VERSION file, so an app always sees POCKETMAGE_SDK_VERSION_* in
pocketmage_app_version.h alongside the host-exported string literal
pocketmage_sdk_version; see app-abi.md.

Icon

<name>_ICON.bin is an optional 200-byte file: a 40x40, 1-bit-per-pixel
bitmap (5 bytes per row, 8 pixels per byte), drawn by the OS as the app icon.
Missing icons fall back to the OS default. make pack requires the file next
to the Makefile; without one the pack step errors.

Translations

An app that shows strings uses its own catalog; see i18n.md. The
short version: put a src/i18n/ tree in your app and merge it via the
generator the way platformio.ini shows in that doc.

Before you ship

Run make clean all then pm check before every commit; pm check is the
gate CI runs, and --host-elf <firmware.elf> pins the host export surface
exactly (see symbols.md). make pack is the deliverable;
verify the tar’s members with tar -tf build/<name>.tar.

pm new myapp scaffolds from the official
PocketMage_App template.
CI also builds a broken_app fixture that intentionally fails the gate:
it references a symbol the host does not export and must fail pm check.