QMK Compiler API 开发指南:解析固件异步编译服务的架构、Worker 流程与接口设计
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
本文基于 QMK 固件仓库中的编译器开发文档,讲解 QMK Compile API 的整体架构(API 服务、Redis 队列、编译 Worker 与 S3 存储的协作方式)、Worker 从拉取任务到产出固件的完整编译链路,以及 API 服务的四个核心 HTTP 接口。读完本篇,你将理解 QMK 在线编译服务的职责划分,能够把握每个接口的行为边界,并了解仓库中键盘元数据与keymap.c生成机制如何支撑这套编译流程,为二次开发或排查编译服务问题建立完整的知识框架。
一、整体架构:客户端只面对 API 服务
QMK Compile API 由几个核心部件组成。理解它们各自的职责边界,是读懂后续代码的前提:
- API Clients(客户端):Web 端配置器、GUI 工具等第三方应用。客户端只与 API 服务交互——在此提交编译任务、查询任务状态、下载编译产物。
- API 服务(API service):所有客户端请求的入口。它负责把编译任务插入 Redis Queue(即 RQ,一个基于 Redis 的任务队列),并负责从 RQ 和 S3 两处查询任务结果。
- Workers(编译 Worker):真正执行构建的后台进程。Worker 从 RQ 拉取新的编译任务,完成编译后,将源码包和固件二进制上传到兼容 S3 协议的存储引擎。
可以概括为一条单向链路:
客户端 ──> API 服务 ──(入队)──> Redis Queue ──> Worker ──(产物)──> S3 存储 客户端 <──(查状态/下载)── API 服务 <──────────(回查结果)────────┘这种设计的要点在于:API 服务本身不做编译,它只是"任务的中转站与结果查询窗口";编译负载完全由可水平扩展的 Worker 承担。任务状态同时存在于 RQ(实时状态)和 S3(任务完成后的缓存快照),这决定了后面状态查询接口的实现方式。
二、Worker:一次编译任务的完整执行步骤
Worker 是整个系统中技术含量最高的部件。从原文档可知,当 Worker 从 RQ 拉取到一个任务后,它会依次执行以下操作来完成这个任务:
- 做一份全新的 qmk_firmware checkout——每次任务都在干净的源码树上编译,避免任务间相互污染;
- 用任务中提供的 layers 和键盘元数据构建
keymap.c——这是"数据驱动"的体现:编译输入不是手写 C 代码,而是结构化的键位数据; - 构建固件;
- 将一份源码打包成 zip;
- 把固件、源码 zip 和元数据文件上传到 S3;
- 把任务状态回报给 RQ。
2.1 键盘元数据从哪里来
第 2 步提到的"键盘元数据"并非凭空产生。在 qmk_firmware 仓库内,API 所需的键盘数据由qmk generate-api命令离线生成。该命令的实现位于 generate_api 函数中,它会:
- 遍历所有键盘目标,为每个键盘调用
info_json()生成一份info.json,写入.build/api_data/v1/keyboards/<keyboard>/info.json; - 汇总出全局数据文件:
keyboards.json(全部键盘的大 JSON)、keyboard_list.json(键盘目标简表)、keyboard_aliases.json(历史键盘名到新名的映射)、usb.json(USB VID/PID 到键盘目标的映射)、keyboard_metadata.json(configurator/via 初始化所需的全部数据)以及constants_metadata.json(可用常量清单); - 将 hjson 与 jsonschema 文件转换/过滤后一并拷贝到输出目录(见 _filtered_copy)。
仓库中 data/templates/api/readme.md 也明确说明:键盘元数据目录"包含 QMK 所支持键盘的机器可解析数据……请勿手工编辑任何内容,它是用qmk generate-api命令生成的"。也就是说,Worker 在编译前拿到的键盘元数据,本质上就是本仓库通过generate-api离线导出、再发布到数据服务的那份 JSON 数据。
描述这类"API 侧键盘数据"的结构约束,见 api_keyboard.jsonschema:它在基础键盘 schema 之上扩展了keymaps.url(JSON 键位的下载地址)、parse_errors/parse_warnings(解析错误与警告)、processor_type(处理器类型)、protocol(通信协议)、keyboard_folder和platform等字段——这些字段正是 Worker 选择编译方式时需要的关键信息。
2.2 keymap.c 是如何生成的
Worker 第 2 步"用 layers 和键盘元数据构建keymap.c",其模板逻辑在仓库中可以直接找到。lib/python/qmk/keymap.py 定义了默认模板DEFAULT_KEYMAP_C:
#include QMK_KEYBOARD_H #if __has_include("keymap.h") # include "keymap.h" #endif __INCLUDES__ /* THIS FILE WAS GENERATED! * * This file was generated by qmk json2c. You may or may not want to * edit it directly. */ __KEYMAP_GOES_HERE__ __ENCODER_MAP_GOES_HERE__ __DIP_SWITCH_MAP_GOES_HERE__ __MACRO_OUTPUT_GOES_HERE__ #ifdef OTHER_KEYMAP_C # include OTHER_KEYMAP_C #endif // OTHER_KEYMAP_C从源码结构看,占位符__KEYMAP_GOES_HERE__会被 tasks 提供的 layers 数据渲染出的keymaps[]数组填充;如果键盘支持旋钮编码器或拨码开关,layers之外的encoders、dip_switches字段也会分别落入对应占位符。这些字段的合法性由 keymap.jsonschema 约束:其中layers要求"字符串的数组的数组"(与模板宏等长),encoders每项必须含ccw/cw两个键码,dip_switches每项必须含on/off,macros则支持键码字符串或{action: beep|delay|down|tap|up, keycodes, duration}形式的结构化动作。这套 schema 就是"API 做基本校验"所依据的规则集。
三、API 服务:一个 Flask 应用与四个核心视图
API 服务本身是一个相对简单的 Flask 应用。文档指出其中有几个主要视图(views)值得开发者理解。
3.1 POST /v1/compile —— 主要入口点
这是 API 的主入口,客户端的交互从这里开始:客户端 POST 一个描述键盘的 JSON 文档,API 对它做一些(非常)基本的校验,然后提交编译任务。
"基本校验"的含义是:此时只验证 JSON 结构(键盘名是否存在、layers 长度是否与LAYOUT宏匹配等),不做编译级校验。真正的编译错误要到任务执行后通过output字段才能看到。
任务的 JSON 载荷应包含构建固件所需的全部信息。以仓库文档 docs/api_docs.md 给出的示例为例:
{ "keyboard": "clueboard/66/rev2", "keymap": "my_awesome_keymap", "layout": "LAYOUT_all", "layers": [ ["KC_GRV","KC_1","KC_2", "...每个键位一个 QMK 键码,长度与 LAYOUT 宏一致..."], ["KC_ESC","KC_F1","KC_F2", "...第二层..."], ["KC_TRNS","KC_TRNS","KC_TRNS", "...第三层..."] ] }载荷描述了一款键盘构建固件所需的一切:keyboard指定目标键盘,keymap是键位图名称,layers中每一层是一组 QMK 键码,其长度必须等于该键盘LAYOUT宏的参数个数;若键盘支持多个LAYOUT宏,可以用layout字段显式指定使用哪一个(如示例中的LAYOUT_all)。
提交成功后,服务返回任务的入队确认,例如:
$ curl -H "Content-Type: application/json" -X POST -d "$(< json_data)" https://api.qmk.fm/v1/compile { "enqueued": true, "job_id": "ea1514b3-bdfc-4a7b-9b5c-08752684f7f6" }job_id是后续所有查询操作的凭证。
3.2 GET /v1/compile/<job_id> —— 最常用的状态查询
这是被调用最频繁的端点。它的实现体现了第一节中"状态双份存放"的架构:优先从 Redis 拉取任务的实时细节(如果还在),Redis 里查不到时再回退到 S3 上缓存的任务细节。这实际上是一个"热数据 + 冷缓存"的组合——Redis 里的任务数据有过期策略,而 S3 上的元数据长期保留,因此历史任务依然可以查询到。
返回体形如:
$ curl https://api.qmk.fm/v1/compile/ea1514b3-bdfc-4a7b-9b5c-08752684f7f6 { "created_at": "Sat, 19 Aug 2017 21:39:12 GMT", "enqueued_at": "Sat, 19 Aug 2017 21:39:12 GMT", "id": "f5f9b992-73b4-479b-8236-df1deb37c163", "status": "running", "result": null }任务共有 5 种可能的状态(来源):
| 状态 | 含义 |
|---|---|
failed | 编译服务本身出了故障 |
finished | 编译完成,应查看result字段获取结果 |
queued | 键位图正在排队,等待编译服务器可用 |
running | 编译正在进行中,很快会完成 |
unknown | 发生了严重错误,需要向项目提交 bug 报告 |
任务完成后,result字段是一个包含若干关键信息的对象:
firmware_binary_url:可烧录固件的 URL 列表;firmware_keymap_url:keymap.c的 URL 列表;firmware_source_url:完整固件源码的 URL 列表;output:该编译任务的 stdout 与 stderr,编译错误就在这里面。
3.3 GET /v1/compile/<job_id>/download —— 下载固件
该视图允许用户下载已编译完成的固件文件,对应result中的firmware_binary_url。
3.4 GET /v1/compile/<job_id>/source —— 下载源码
该视图允许用户下载其固件的源码,即 Worker 上传到 S3 的那份源码 zip(含生成的keymap.c与完整工程),对应result中的firmware_source_url。
从源码组织上可以印证 Worker 的职责划分:下载端能拿到的两类产物——二进制与源码包——正是 Worker 流程中"Zip a copy of the source"与"upload the firmware"两个步骤的对应输出。
四、常量数据面:面向工具的"锁定版"键码常量
除了编译任务本身,API 还承担向第三方工具发布"锁定"常量的职责(文档说明):编写依赖 QMK 常量的工具时,可以通过端点获取锁定版本的常量清单,确保第三方工具基于同一份权威数据工作。
- 通过
constants_metadata.json端点获取可用常量清单(master与develop分支各有一份);master上导出的版本是锁定版,develop上多出的版本可能随时变化; - 获取某个子系统的常量使用
constants/{subsystem}_{version}.json形式的端点,例如constants/keycodes_0.0.1.json,返回内容包括ranges(如0x0000/0x00FF对应QK_BASIC、0x0100/0x1EFF对应QK_MODS)等键码分段定义。
这套常量的生产端同样在 qmk_firmware 仓库内:generate_api 中的_resolve_keycode_specs()会把各版本的键码规格预合并为keycodes_{version}.json(以及各语言版本keycodes_{lang}_{version}.json),供 API 数据面直接发布。
五、开发者入手路径与适用前提
对于想在 API 本身(而非客户端)上做开发的人员,文档给出的路径是:先搭建开发环境(原文档指向独立的 qmk_web_stack 项目,该栈包含 API 服务与 Worker 的部署配置,不属于本仓库),再结合本篇与代码阅读来理解整体行为。
几点适用前提需要说明:
- 本篇描述的是 QMK 官方编译服务(api.qmk.fm / keyboards.qmk.fm 数据面)的架构与接口约定,其服务代码不在 qmk_firmware 仓库内;本仓库提供的是该服务所依赖的数据与元数据生产端(
generate-api、data/schemas/、键码常量规格); - 键盘元数据目录的内容由
qmk generate-api生成,不应手工编辑; - 状态与结果字段的具体取值以 docs/api_docs.md 为准,键盘元数据结构以 data/schemas/api_keyboard.jsonschema 为准。
小结
QMK Compile API 用"Flask 网关 + RQ 队列 + 编译 Worker + S3 存储"的组合,把编译这种重负载操作从 Web 服务中彻底剥离:API 服务只负责入队、状态回查(Redis 热数据 + S3 冷缓存)和产物下载;Worker 以干净 checkout、数据驱动的keymap.c生成和标准化上传流程保证每次构建的可复现性;而 qmk_firmware 仓库内的generate-api与 JSON Schema 则构成了整套体系的数据根基。掌握这三个层面,再读 API 与 Worker 源码时就有了完整的挂载框架。
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考