xt-sdk 文档 v1.0.3
xt-sdk 嵌入式 SDK API 参考
载入中...
搜索中...
未找到
xt-sdk 构建系统设计梳理(此文档已过时)

文档版本 修改日期 修改内容
v1 2026-05-09 调整为通用设计梳理文档,修正 builddir 相关结论,补充装配流程、组件覆盖、循环依赖和版本注入设计。
v2 2026-06-16 重构:溶解 xt_core 为平台聚合层、platforms/components/ 收敛为单一 xtiny target 聚合所有 API 头文件路径(平台无关叶子节点)、引入 标准工具库 标准库、废弃 xt_port/xt_sdk_target/xt_sdk_platform_set_enable。平台实现侧通过 add_deps("xtiny") 统一拿 API 头文件,规避循环依赖。

一、文档目的

梳理 xt-sdk xmake 构建系统 v2 的设计,覆盖:

  1. 顶层装配流程与加载顺序
  2. platforms/components/ 的 xtiny 单一聚合机制
  3. xt_core / xtiny / 标准工具库 的职责与依赖关系
  4. target 默认不参与编译的设计
  5. 用户组件覆盖系统组件的机制
  6. 版本信息注入
  7. 平台侧和工程侧职责划分

二、整体结构

v2 采用"API 定义 → 平台实现 → 标准组件 → 工程"四层装配:

flowchart TD
    A[1. platforms/components/] --> B[2. platforms/<plat>/<target>/]
    B --> C[3. components/]
    C --> D[4. 用户工程]
    D --> E[5. platforms/<plat>/xmake.lua]

顶层的实际装配顺序:

-- 1. xt API 接口定义层
includes(path.join(sdk_root, "platforms/components"))
-- 2. 平台 target 目录(定义 xt_core 聚合 + includes 平台实现)
includes(target_path)
-- 3. SDK 标准组件
includes(path.join(sdk_root, "components"))
-- 4. 用户工程
includes(project_path)
-- 5. 最终构建产物(平台级 xmake.lua)
includes(path.join(target_path, ".."))

这个顺序的关键含义:

  • xtiny 先就位(平台无关叶子节点,仅依赖 标准工具库),所有平台实现 target 可依赖它拿全部 API 头文件路径
  • 平台 target 通过 includes("../components") 加载平台实现 target
  • 标准组件在平台 target 之后,可以依赖 xt_core
  • 用户工程最后声明,通过 add_deps("xt_core") 接入

三、核心 target 层次关系

3.1 SDK API 层(platforms/components/)

platforms/components/ 不再为每个 xt_xxx API 单独创建 target。整个目录收敛为**唯一一个** target:xtiny。

-- platforms/components/xmake.lua
local component_dirs = os.dirs("*")
table.sort(component_dirs)
target("xtiny")
set_kind("headeronly")
add_deps("xt_std")
for _, component_dir in ipairs(component_dirs) do
local component_name = path.basename(component_dir)
add_includedirs(component_name, {public = true})
end
target_end()

xtiny 关键特征:

  • set_kind("headeronly")——不产生 .a
  • add_deps("标准工具库")——仅依赖标准库,平台无关的叶子节点
  • add_includedirs(<每个子目录>, {public = true})——遍历 platforms/components/ 下所有子目录,把每个 API 头文件目录(xt_hal/、xt_log/、xt_audio/...)作为公开 include 路径暴露

xtiny 是仅次于 标准工具库 的最小系统接口聚合点。任何依赖它的 target 都能直接 #include "xt_hal.h"、#include "xt_log.h" 等,无需关心来自哪个 target。

3.2 平台装配层(platforms/<plat>/<target>/)

flowchart TD
    app["app (binary)"] --> xt_main["xt_main (用户工程)"]
    xt_main --> xt_core["xt_core (static)"]
    xt_core --> xtiny
    xt_core --> impls["各实现 target (static)"]
    impls --> xtiny
    impls --> priv["平台私有 target (xt_soc 等)"]

xt_core 由 platforms/<plat>/<target>/xmake.lua 定义,将一个平台需要的能力 target 聚合为单一依赖入口。以 Windows 平台为例:

-- platforms/windows/simulator/xmake.lua
includes(path.join(os.scriptdir(), "../components")) -- 加载平台实现
target("xt_core")
set_kind("static")
add_deps("xtiny")
add_deps("platform_entry")
add_deps("cmsis_os2")
add_deps("xt_hal")
add_deps("xt_log")
add_deps("xt_mem")
-- ... 其余平台能力
target_end()

平台实现 target 模板(以 xt_hal(HAL 驱动总入口) 为例):

-- platforms/<plat>/components/xt_hal/xmake.lua
local TARGET_NAME = path.basename(os.scriptdir())
target(TARGET_NAME)
set_kind("static")
add_deps("sdk-headers")
add_deps("xtiny") -- 拿全部 API 头文件路径
add_deps("xt_soc") -- 平台内部依赖(拿 platform_private.h 等)
add_files(path.join(os.scriptdir(), "*.c"))
target_end()

3.3 xtiny 单一聚合机制(替代旧同名 target merge)

**为什么放弃同名 target merge**:

旧设计下,每个 xt_xxx API target 独立存在(xt_hal(HAL 驱动总入口)、xt_log 等),平台实现侧通过**同名 target merge** 关联。这导致循环依赖无法根治:

同名 merge 把"API 头文件路径"和"实现层依赖"绑死在同一个 target,业务上必然出现的双向引用无法解耦。

**xtiny 单一聚合如何解决**:

把"API 头文件路径"从每个 xt_xxx target 抽离,统一收口到 xtiny 这个 headeronly target。xtiny 不依赖任何平台实现,是依赖图的叶子节点。

xtiny 通过 add_includedirs(<每个子目录>, {public=true}) 显式列举所有 API 子目录,不依赖每个 API target 自身的 public include 传播——这是设计能成立的关键。

四、环境变量与 env.lua

4.1 当前依赖的环境变量

顶层 xmake.lua 当前依赖:

环境变量 作用
XT_SDK_ROOT SDK 根目录
XT_SDK_PROJECT_PATH 用户工程路径
XT_SDK_TARGET_PATH 平台 target 路径
XT_SDK_TOOLCHAIN 工具链路径

其中 XT_SDK_PROJECT_PATH、XT_SDK_TARGET_PATH、XT_SDK_TOOLCHAIN 都是当前构建的关键前提。

4.2 scripts/env.lua 的职责

scripts/env.lua 当前做了 4 件事:

  1. 从 XT_SDK_TARGET_PATH 中拆出 <platform>/<target>
  2. 把环境归一化成统一结构
  3. 读取 XT_SDK_BOARD 与 XT_SDK_CONFIG_HEADER,其中 board 会做小写下划线规范化,config_header 保留原值
  4. 通过 xt_sdk_require_env() 校验关键环境项,并通过 xt_sdk_build_dir() 计算产物目录

xt_sdk_build_dir() 当前返回:

<project_path>/.build/<platform>_<target>

也就是说,它已经明确把产物目录绑定到工程目录,而不是 SDK 根目录。

4.3 当前缺口

env.lua 当前负责"解析、归一化和校验",但不负责统一 xmake 的配置缓存目录、工作目录和中间文件路径语义。因此:

  • 工程目录和当前执行目录仍然不是一个概念
  • 顶层脚本里 builddir 等相对路径行为,仍会受到当前工作目录影响

五、为什么当前必须切到示例目录

README 中写"目前必须切到示例目录",这一点当前实现上是成立的。

根因不是 targetdir / objectdir,而是顶层 xmake.lua 里这一句:

set_config("builddir", ".build")

这里的 .build 是相对路径,相对的是当前工作目录,而不是 XT_SDK_PROJECT_PATH。虽然顶层后面又通过 xt_sdk_build_dir() 设置了:

  • set_targetdir(build_dir)
  • set_objectdir(path.join(build_dir, ".objs"))

但 xmake 内部仍有一部分配置和临时文件受 builddir 的相对路径影响。实际试过把它改成绝对路径时,xmake 内部又会把它转回相对路径,从而导致 .d 等文件的位置出现问题。

因此当前阶段 README 的结论应保持不变:

如果直接用 xmake 构建,必须先切到示例目录,再执行顶层 xmake.lua。

这一点不是操作习惯,而是当前实现的现实约束。

六、target 默认不参与编译的设计

顶层 xmake.lua 重写了 target(name)。它做的事情不是改 target 的本义,而是给每个被声明的 target 先隐式加上一层:

set_default(false)

这样做的目的是:

组件被 includes() 进来,不等于它就应该参加默认构建。

否则只要被扫描到的组件,都有机会进入默认编译链,工程就很难控制真正需要的依赖集合。

最终二进制 target(如 app)通过 set_default(true) 把整条依赖链拉起来。v2 不再需要 v1 的 xt_sdk_platform_set_enable 中转。

七、用户组件覆盖系统组件

当前用户组件覆盖系统组件,不是通过替换文件完成的,而是通过 target 名优先级实现的。

流程如下:

  1. 顶层先 includes(project_path)
  2. 工程若先声明了同名 target,会被记录下来
  3. 之后 components/xmake.lua 和 platforms/components/xmake.lua 扫描系统组件目录时,会跳过这些已声明的同名 target

这样用户组件就自然覆盖了系统同名组件。

这个机制已经有正式示例:

八、循环依赖规避

平台实现需要 SDK 头文件,但 target 依赖保持单向:

  • xtiny(platforms/components/)仅 add_deps("标准工具库"),是平台无关的叶子节点
  • 平台实现 target 通过 add_deps("xtiny") 拿到全部 API 头文件路径
  • 跨组件的源码引用(如 xt_hal(HAL 驱动总入口) 调用 xt_soc 的私有函数)通过 add_deps 显式声明实现层依赖
  • 不存在 SDK 标准库 → 平台实现的反向依赖

通过 xtiny 收口 API 头文件路径 + 单向实现层依赖:

允许 API 头文件全局可见,不允许 target 图形成闭环。

xtiny 本身不依赖任何平台实现 target,所以平台实现之间的相互引用不会通过 xtiny 形成环。

九、版本信息注入

顶层 xmake.lua 会在 on_config 里处理版本信息。优先级是:

  1. 先读 .xt_sdk_release_info.lua
  2. 如果没有,再读 SDK 仓库的 git 状态
  3. 如果平台目录上一级是独立 git 仓库,再单独读平台仓库状态
  4. 最后把结果写进 xmake config

最终由 xt_sdk_version(SDK 版本号) 组件把这些 config 导出为公开宏。

这里解决的是两件事:

  • SDK 版本号和 git 状态注入
  • 平台如果是独立仓库时,平台版本也能单独注入

十、平台侧和工程侧职责划分

10.1 平台侧职责

  • 工具链配置
  • platforms/components/ API 定义
  • platforms/<plat>/components/ 平台实现
  • platforms/<plat>/<target>/ xt_core 聚合
  • platforms/<plat>/xmake.lua 最终产物 target
  • 平台特有链接参数

10.2 工程侧职责

十一、避坑指南

11.1 xtiny 的使用约定

xtiny 是仅次于 标准工具库 的最小系统接口聚合点,定位上升到平台 API 头文件库——所有 API(xt_hal、xt_log、xt_audio 等)的头文件路径都通过它传播。

**xtiny 的硬性使用方式**:平台实现 target 想拿 API 头文件,必须 add_deps("xtiny")。这是构建机制的硬性要求。

**xtiny 的隐性使用约定**(约定俗成,非机制强制):

  • **用户应用工程**:无需关注 xtiny。add_deps("xt_core") 即可,xt_core 已聚合 xtiny
  • **平台实现组件**(platforms/<plat>/components/ 下的 xt_xxx 实现):可以 add_deps("xtiny") 拿全部 API 头文件
  • **业务应用组件**(如 oled 屏驱动、传感器驱动等用户开发的组件):约定**不**直接 add_deps("xtiny"),而是按需 add_deps 具体的 xt_xxx 实现组件(如 add_deps("xt_hal(HAL 驱动总入口)"))。这样依赖关系更精准,组件源码不会"看见"无关 API 头文件

**关于隐性约定**:旧设计(同名 merge)下 xtiny 不能被平台组件依赖,否则破坏循环依赖规避。新设计下 xtiny 已经是叶子节点,平台组件依赖它不会形成循环。但"业务组件按需依赖具体 xt_xxx"仍然是推荐做法——纯粹出于依赖关系可读性,而非硬性约束。

11.2 平台实现组件的 include 约定(重点)

虽然 xtiny 单一聚合了所有 API 头文件路径,任何 add_deps("xtiny") 的 target 编译器都能"看见"全部 API 头文件,但**平台实现组件源码仍应仅 #include 与本组件职责相关的头文件**。

**核心规则**:

  • **不直接 #include "xtiny.h"**:xtiny.h 是面向用户应用工程的总头文件入口,会一次性拉入全部 API 头。平台实现组件应按需 include 具体的 API 头文件(xt_hal.h、xt_log.h 等)。
  • **仅 include 本组件职责相关的头文件**:例如 xt_hal/xt_hal_uart.c 只应 #include "xt_hal.h" 和必要的私有头,不应 #include "xt_audio.h"、#include "xt_mobile.h" 等无关头文件。

**为什么这条很重要**:

  • xtiny 暴露路径是构建层面的便利,不代表源码层应该滥用
  • 仅 include 相关头文件能保证:组件边界清晰、依赖关系可读、未来若 xtiny 拆分时迁移成本最小
  • 平台实现直接 include xtiny.h 会让"这个组件到底用了哪些 API"难以从源码层面判断

**反面例子**(违反约定):

/* platforms/lm620_internal/components/xt_hal/xt_hal_uart.c */
/* 错误:直接 include xtiny.h,拉入全部 API */
#include "xtiny.h"
/* 错误:滥用 xtiny 暴露的全部路径 */
#include "xt_audio.h" /* 与 uart 无关 */
#include "xt_mobile.h" /* 与 uart 无关 */
#include "xt_hal.h" /* 唯一相关的头 */
XTINY HAL 总头文件,包含所有 HAL 子模块
XTINY 蜂窝网络模块接口
XTINY 总头文件

**正面例子**(遵循约定):

/* platforms/lm620_internal/components/xt_hal/xt_hal_uart.c */
/* 正确:仅 include 实际需要的头 */
#include "xt_hal.h"
#include "platform_private.h" /* 平台私有依赖,通过 add_deps("xt_soc") 拿到 */

**当前已知不规范用法**:platforms/lm620_internal/components/ 下部分源码(如 xt_hal/xt_hal_internal.h、platform_entry/main.c 等)直接 #include "xtiny.h",属于历史遗留的不规范用法,未来会逐步改造为按需 include 具体头文件。新组件应从一开始就遵循本约定。

**机制 vs 约定**:xtiny 的聚合保证头文件路径全局可见(机制),但源码层的 include 仍受组件职责约束(约定)。两者不冲突——前者解决构建便利和循环依赖,后者保证代码组织清晰。

11.3 废弃项速查

v1 设计 v2 替代
xt_sdk_platform_set_enable 二进制 target 直接 set_default(true)
xt_sdk_platform phony 转发 二进制 target 自身作为入口
xt_port / xt_sdk_target 平台实现直接成独立 static target
$(xt_core_path) 头文件路径 xtiny target 的 public include 传播
xt_core 作为 SDK 核心组件 标准工具库 作为标准库,xt_core 作为平台聚合入口
同名 target merge 关联 API 与实现 xtiny 单一聚合 API 头文件路径

11.4 builddir 工作目录依赖

set_config("builddir", ".build") 的相对路径行为决定了当前仍须切到示例目录执行。这是 v1 的遗留约束,v2 未改变。

十二、当前稳定机制

  1. 顶层装配顺序(platforms/components → target → components → 工程 → 平台)
  2. xtiny 单一 target 聚合所有 API 头文件路径,平台无关的叶子节点
  3. public = true 传播 include 路径和宏
  4. 用户 target 优先于 SDK 同名 target
  5. 单向依赖(xtiny 收口头文件路径)规避循环依赖
  6. 版本信息统一在顶层注入
  7. xt_core 作为平台能力清单(deps 即文档)

附录:v1 vs v2 架构对照

维度 v1 v2
API 层 xt_core 组件内嵌 platforms/components/ 收敛为 xtiny 单一 target
标准库 xt_core/components/utils/ components/xt_std/
平台实现 xt_port phony → xt_sdk_target 独立 static target,add_deps("xtiny") 拿 API 头文件
平台入口 xt_sdk_platform phony xt_core static 聚合
启用机制 xt_sdk_platform_set_enable 二进制 target set_default(true)
用户依赖 add_deps("xt_core")(SDK 组件) add_deps("xt_core")(平台聚合)
总头文件 components/xt_core/xtiny.h platforms/components/xtiny/
加载顺序 target → 工程 → components → 平台 platforms/components → target → components → 工程 → 平台
循环依赖 $(xt_core_path) include path xtiny 收口 API 头文件路径