Flipper Zero AppManifests 完全指南:application.fam 清单格式与 fbt 构建系统深度解析
2026/9/14 7:55:42 网站建设 项目流程

Flipper Zero AppManifests 完全指南:application.fam 清单格式与 fbt 构建系统深度解析

【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware

Flipper Zero 固件中的每一个组件——系统服务、主菜单应用、设置项、调试工具乃至 SD 卡上的外部应用——都通过一个名为application.fam的清单文件在构建系统中声明其身份与依赖。本文以 documentation/AppManifests.md 为骨架,结合仓库内application.fam实例与 fbt 构建工具、脚本/fbt/appmanifest.py 的源码实现,完整讲解 FAM 清单的全部参数、外部应用(FAP)专属字段、依赖与冲突处理机制,帮助你写出可被fbt正确识别、编译与打包的应用清单。

FAM 在构建系统中的定位

Flipper Zero 固件的所有组件——服务、用户应用与系统设置——均独立开发,每个组件对应一份名为application.fam的清单文件,用于定义该组件的基本属性及其与系统其他部分的关系。

构建固件时,fbt会收集所有应用清单并处理其依赖关系,随后只构建当前构建配置中实际引用的组件(详见 FBT 文档)。因此,application.famfbt理解"有哪些应用、各自需要什么"的唯一入口——没有清单的应用不会被构建,清单中声明的依赖决定了编译时的可用符号与源码集合。

从源码结构看,清单的实际处理逻辑集中在 scripts/fbt/appmanifest.py:FlipperAppType枚举定义了全部应用类型,FlipperApplication数据类声明了所有可用的字段与默认值,AppManager负责加载并校验清单(重复的appid会直接抛FlipperManifestException),AppBuildset则完成依赖解析、冲突检查与目标平台匹配。

App 定义与必填参数

一个固件组件的属性通过一段 Python 代码片段声明,即调用带各种参数的App()函数。清单文件可包含一个或多个App()定义,例如 applications/services/bt/application.fam 同时定义了蓝牙服务与蓝牙设置两个组件。

只有两个参数是必填的:

  • appid:字符串,构建系统内的应用 ID,用于指定构建配置中包含哪个应用,以及解析依赖与冲突。fbt强制要求其匹配正则^[a-z0-9_]+$(见 scripts/fbt/appmanifest.py#L35),即只能使用小写字母、数字与下划线,且不允许重复声明。
  • apptypeFlipperAppType.*枚举的成员,决定组件的固件角色,详见下表:
枚举成员固件组件类型
SERVICE系统服务,在系统启动早期创建
SYSTEM不出现在任何菜单中的应用,可由其他应用或 CLI 启动
APP主菜单中的常规应用
PLUGIN作为固件一部分构建、放置于插件菜单中的应用
DEBUG仅在开启调试模式时于调试菜单中可见的应用
ARCHIVE唯一且仅有的 Archive 应用
SETTINGS放置在系统设置菜单中的应用
STARTUP系统启动时运行的回调函数,不定义独立应用
EXTERNAL构建为.fap插件的外部应用
METAPACKAGE不定义任何要运行的代码,用于声明依赖与应用捆绑包

值得注意的是,scripts/fbt/appmanifest.py#L19-L30 中还存在一个文档表格未列出的成员MENUEXTERNAL,它属于外部应用类型,但按 AppBuildset 定义 仅在显式列入应用集合时才构建(always deployFalse)。

通用参数详解

appidapptype外,其余参数均为可选,且只对特定应用类型有意义:

  • name:菜单中显示的名称。例如蓝牙设置应用在菜单中显示为 "Bluetooth"(见 applications/services/bt/application.fam#L127)。
  • entry_point:用作应用入口的 C 函数名。C++ 函数名会被编译器改编(mangling),如需作为入口点必须用extern "C"包裹。
  • flags:系统应用的内部标志,普通开发不应使用。
  • cdefines:当当前应用被包含进激活的构建配置时,为其他应用全局声明的 C 预处理器定义;对外部应用:这些定义仅用于构建该应用自身。典型例子如蓝牙服务声明cdefines=["SRV_BT"](见 applications/services/bt/application.fam#L6)。
  • requires:应用 ID 列表,当当前应用被列入待构建应用清单时,这些应用也会被加入构建配置。
  • conflicts:应用 ID 列表,若构建出的应用列表中出现任一冲突应用,fbt将中止固件构建。
  • provides:功能上与requires完全一致,常用于将若干应用打包声明为"提供"集合(见下文 METAPACKAGE 示例)。
  • stack_size:应用启动时分配的栈大小(字节)。栈分配过小会导致栈溢出引发系统崩溃,过大则会减少应用可用的堆内存。可用topfreeCLI 命令分析应用内存占用。源码默认值为2048字节(见 scripts/fbt/appmanifest.py#L62)。
  • icon:作为固件一部分构建时,从内置资源中选取的动画图标名。
  • order:应用在其所属分组内的排序值,值越小越靠前,用于排序启动钩子与菜单项。蓝牙服务与蓝牙设置的order分别为 20 与 40(见 applications/services/bt/application.fam)。
  • sdk_headers:该应用代码中需要包含进外部应用 API 定义的 C 头文件列表。蓝牙服务声明了bt_service/bt.hbt_service/bt_keys_storage.h(见 applications/services/bt/application.fam#L17)。
  • targets:应用兼容的目标平台名列表,未指定时默认为["all"],即所有目标。仓库中硬件相关的调试工具常显式限定目标,例如 applications/debug/accessor/application.fam#L5 声明targets=["f7"]。判断逻辑见 scripts/fbt/appmanifest.py#L97-L98:当目标名在targets中或targets"all"时视为支持。
  • resources:应用源码文件夹内用于打包 SD 卡资源的子文件夹名,仅当应用被包含进构建配置时才生效,默认值""表示不打包资源。

METAPACKAGE:依赖声明与应用捆绑包

METAPACKAGE类型不产生任何可运行代码,专门用于声明依赖集合,是组织固件应用清单的常用手段。例如 applications/main/application.fam 中的main_apps元包通过provides一次性声明了主菜单全部核心应用(gpio、ibutton、infrared、lfrfid、nfc、subghz、bad_usb、u2f、archive 等);applications/debug/application.fam 中的debug_apps元包则把 blink_test、vibro_test、keypad_test 等全部调试应用捆绑在一起。

这些元包 ID 正是 fbt_options.py 中 FIRMWARE_APPS 引用的集合名(如"main_apps""system_apps""settings_apps")。由于requiresprovides都会触发依赖解析(见 scripts/fbt/appmanifest.py#L303-L334 的_process_deps循环),引用一个元包即可递归带入其提供的全部应用。

PLUGIN 与外部应用的构建约束

fbt对 PLUGIN 与普通内建应用有不同的参数约束,校验逻辑位于 scripts/fbt/appmanifest.py#L144-L173:

  • PLUGIN 必须声明requires:插件必须通过requires指明宿主应用(父应用),缺少该字段会直接报错。
  • PLUGIN 不能设置stack_size:插件共享宿主应用的进程上下文,stack_size会被强制置 0(见 scripts/fbt/appmanifest.py#L108-L110),若显式传入 stack 值会提示"是否本意是 EXTERNAL 类型"。
  • fal_embedded仅适用于 PLUGIN:普通应用设置该字段会被拒绝。
  • 外部分发类应用(EXTERNAL、PLUGIN、DEBUG)不能使用resources字段(资源打包另有fap_file_assets机制);内建应用则不能使用fap_extbuildfap_private_libsfap_icon_assets等 FAP 专属字段。

实际示例可参考 applications/examples/example_plugins/application.fam:example_pluginsexample_plugins_multi是 EXTERNAL 宿主应用,example_plugin1example_plugin2是 PLUGIN,分别通过requires=["example_plugins", "example_plugins_multi"]requires=["example_plugins_multi"]挂载到宿主上,并通过sources=["plugin1.c"]sources=["plugin2.c"]指定各自的源码文件。插件归属关系由AppBuildset._group_plugins处理(见 scripts/fbt/appmanifest.py#L397-L423)。

外部应用(FAP)专属参数

以下参数仅用于构建 FAP(Flipper App Package,即.fap文件,可独立于固件版本运行,详见 AppsOnSDCard.md):

  • sources:字符串列表,用于在应用文件夹内收集源文件的文件名掩码,默认值为["*.c*"](同时包含 C 与 C++ 源码)。应用不能使用"lib"文件夹存放自身源码,因为它被保留给fap_private_libs。以"!"开头的路径从源文件列表中排除,掩码可含通配符与目录名。例如["*.c*", "!plugins"]会收集应用文件夹内除plugins(及lib)文件夹外的全部 C/C++ 源码;不含通配符(*?)的路径则按完整字面路径处理。
  • fap_version:字符串,应用版本,默认"0.1",也可使用(x, y)形式的二元元组。版本可追加更多点分隔部分(如补丁号),但只有主版本号与次版本号会被写入构建出的.fap。源码中会将其解析为整数元组并校验至少两个分量(见 scripts/fbt/appmanifest.py#L115-L123)。
  • fap_icon.png文件名,要求 1 位色深、10x10 像素,嵌入.fap文件内部。
  • fap_libs:额外链接库列表,可访问未作为主固件 API 导出的额外函数,代价是.fap文件体积与 RAM 消耗增加。
  • fap_category:字符串,可为空,应用子分类,同时决定 FAP 在文件系统 apps 文件夹中的存放路径。
  • fap_description:字符串,可为空,应用简介。
  • fap_author:字符串,可为空,应用作者。
  • fap_weburl:字符串,可为空,应用主页。
  • fap_icon_assets:字符串,定义收集应用图片资源的文件夹名,这些图片会被预处理并随应用一起构建,使用方式见 AppsOnSDCard.md 的 FAP assets 章节。
  • fap_extbuild:支持应用的部分源码由外部工具构建,包含一组ExtFile(path="文件名", command="shell 命令")定义,fbt会为列表中的每个文件执行对应命令。
  • fal_embedded:布尔值,默认False,仅适用于 PLUGIN 类型。若为True,插件会作为资源嵌入宿主应用的.fap文件,宿主启动时解压到apps_assets/APPID文件夹,从而随宿主应用一并分发。

fap_extbuild 与 Rust 构建示例

外部构建命令在固件根目录执行,所有中间文件必须放在应用的临时构建文件夹中。为此可借助fbt的模式展开:${FAP_WORK_DIR}替换为应用临时构建文件夹路径,${FAP_SRC_DIR}替换为应用源码文件夹路径,也可使用fbt内部定义的其他变量。

以下示例展示了如何从 Rust 源码构建应用(引自 documentation/AppManifests.md#L64-L74):

sources=["target/thumbv7em-none-eabihf/release/libhello_rust.a"], fap_extbuild=( ExtFile( path="${FAP_WORK_DIR}/target/thumbv7em-none-eabihf/release/libhello_rust.a", command="cargo build --release --verbose --target thumbv7em-none-eabihf --target-dir ${FAP_WORK_DIR}/target --manifest-path ${FAP_SRC_DIR}/Cargo.toml", ), ),

即先用cargo在临时目录中交叉编译出静态库,再将其作为应用源码链接进.fap

fap_private_libs 与私有库示例

fap_private_libs:随应用以源码形式分发的额外库列表,这些库会作为应用构建过程的一部分被编译。库源码必须放在应用源码文件夹内的lib子文件夹中。每个库通过调用Lib()函数定义,参数如下:

  • name:库文件夹名称。必填。
  • fap_include_paths:加入父应用 include 路径列表的库相对路径,默认["."],即库源码根目录。
  • sources:收集库源码的文件名掩码列表,路径相对库源码根目录,默认["*.c*"]
  • cflags:构建该库时附加的编译器标志列表,默认[]
  • cdefines:构建该库时附加的预处理器定义列表,默认[]
  • cincludes:构建该库时附加的 include 路径列表,路径相对应用根目录,可用于为库代码提供外部搜索路径(如配置头文件),默认[]

以下示例(引自 documentation/AppManifests.md#L89-L106)同时声明了两个私有库:

fap_private_libs=[ Lib( name="mbedtls", fap_include_paths=["include"], sources=[ "library/des.c", "library/sha1.c", "library/platform_util.c", ], cdefines=["MBEDTLS_ERROR_C"], ), Lib( name="loclass", cflags=["-Wno-error"], ), ],

对于该片段,fbt将构建两个库:一个来自lib/mbedtls文件夹的源码,另一个来自lib/loclass文件夹。对mbedtls库,fbt会把lib/mbedtls/include加入应用 include 路径,只编译sources列表指定的文件,并为其源码启用MBEDTLS_ERROR_C预处理器定义;对loclass库,fbt会把lib/loclass加入应用 include 路径并构建该文件夹内全部源码,同时禁用"将编译警告视为错误",这在编译大型第三方代码库时非常实用。两个库最终都会与应用链接。

.fam 文件内容与完整实例

.fam文件包含一个或多个应用定义。以下为 applications/services/bt/application.fam 的完整内容,它同时展示了 STARTUP 与 SETTINGS 两种应用类型(文档示例中的order=70在仓库当前版本中为 40,以仓库实际文件为准):

App( appid="bt", name="BtSrv", apptype=FlipperAppType.SERVICE, entry_point="bt_srv", cdefines=["SRV_BT"], requires=[ "cli", "dialogs", ], provides=[ "bt_start", "bt_settings", ], stack_size=1 * 1024, order=20, sdk_headers=["bt_service/bt.h", "bt_service/bt_keys_storage.h"], ) App( appid="bt_start", apptype=FlipperAppType.STARTUP, entry_point="bt_on_system_start", order=40, )

这个实例覆盖了常见参数的典型用法:requires声明运行依赖(CLI 与对话框服务)、provides提供对子组件(启动钩子、设置应用)的引用、sdk_headers导出供外部应用使用的头文件。仓库内其余.fam文件(如 applications/examples/example_plugins/application.fam、applications/examples/example_adc/application.fam)可作为编写清单的更多参考。

fbt 对清单的处理流程

结合 scripts/fbt/appmanifest.py 的源码,fbt对清单的处理可归纳为以下流程:

  1. 加载与语法检查AppManager.load_manifest读取application.fam,以exec方式执行其 Python 代码,App()ExtFile()Lib()三个函数在此上下文注册。清单语法错误或未产生任何App()定义都会报错(见 scripts/fbt/appmanifest.py#L175-L214)。
  2. 参数校验_validate_app_params按应用类型检查字段合法性(PLUGIN 必须requires、禁止stack_size;内建应用禁止 FAP 专属字段等),__post_init__校验appid正则与fap_version格式。
  3. 依赖解析AppBuildset._process_deps反复遍历应用集合,将requiresprovides指向的应用迭代加入集合,直至不再新增(见 scripts/fbt/appmanifest.py#L325-L334)。
  4. 外部应用归类_process_ext_appsEXTERNAL_APP_TYPES_MAP收集外部应用,并根据硬件目标兼容性分成可构建与不兼容两组。
  5. 冲突检查_check_conflicts检查conflicts字段,发现冲突即以App conflicts for ...中止构建;_check_unsatisfied检查requires中缺失的应用。
  6. 目标平台匹配_check_target_match确保选中应用均支持当前构建目标,supports_hardware_target依据targets字段判断。
  7. 插件分组_group_plugins将 PLUGIN 挂载到其requires指定的宿主应用名下,供后续嵌入或菜单组织使用。

构建配置层面,fbt_options.py 中的FIRMWARE_APPS定义了"default""unit_tests"两套应用集合(分别引用basic_servicesmain_appssystem_appssettings_apps等元包 ID),FIRMWARE_APP_SET选择实际使用的集合;命令行可通过--extra-int-apps--extra-ext-apps强制追加内建或外部应用(详见 fbt.md)。

常见错误与排查建议

  • Invalid appidappid包含大写字母、连字符等非法字符,未匹配^[a-z0-9_]+$。统一使用小写蛇形命名。
  • Duplicate app declaration:两个清单声明了相同appid,全局唯一。
  • Plugin ... cannot have stack:PLUGIN 类型误设stack_size,若需要独立栈应改用 EXTERNAL 类型。
  • Plugin ... must have 'requires':PLUGIN 未声明宿主应用。
  • App conflicts for ...:构建集合中出现conflicts冲突组合,需调整应用集合或移除冲突声明。
  • Unsatisfied dependencies for ...requires引用的appid不存在,检查拼写或确认该应用是否已声明清单。
  • App manifest ... is malformed:清单文件语法错误或未包含任何App()调用。
  • Skipping ... due to target mismatch:应用targets不含当前构建目标,属预期跳过行为而非错误。

这些校验信息大多直接来自 scripts/fbt/appmanifest.py 中的异常与提示文本,遇到构建失败时可按提示逐项核对清单字段。

【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询