本项目的构建系统 CMakeLists.txt 是实现跨平台编译和“一键构建”的核心。它不仅能根据目标平台(Linux PC, Windows, 嵌入式 ARM)链接不同的库,更精妙的是,它能在编译前自动修改 LVGL 的配置文件,使开发者无需手动在 lv_conf.h 和 lv_drv_conf.h 中切换宏定义。
核心设计思想
- 平台自动化检测: 通过 CMake 内置变量(如
CMAKE_CROSSCOMPILING,WIN32)自动识别当前是为哪个平台进行编译。 - 依赖隔离与预置:
- 对于 Windows 和 嵌入式 ARM 平台,项目在
libs/目录下预置了编译好的静态库 (.a),实现了“开箱即用”,用户无需在系统上安装 SDL2 或 Freetype。 - 对于 Linux PC 平台,则采用标准的
find_package方式,使用系统中已安装的开发库,符合 Linux 的开发习惯。
- 对于 Windows 和 嵌入式 ARM 平台,项目在
- 配置自动化修改: 这是本构建系统最高级的特性。它通过读取、正则替换、重写
lv_conf.h和lv_drv_conf.h文件,自动启用或禁用 SDL, Framebuffer, evdev 等驱动,彻底免去了手动配置的麻烦。
脚本逐段详解
1. 项目基本设置
1 | cmake_minimum_required(VERSION 3.5) |
project(main C): 定义项目名称为main,语言为 C。if(WIN32): 一个非常贴心的检查。由于 Windows 平台对非 ASCII 字符路径的支持不佳,这里会检测项目路径是否包含中文等字符,并在发现时报错,避免后续难以排查的编译问题。CMAKE_RUNTIME_OUTPUT_DIRECTORY: 指定生成的可执行文件(如main.exe或main)统一存放在项目根目录下的bin/文件夹中,保持根目录整洁。
2. 核心:平台检测与依赖配置
这是实现跨平台链接的关键部分。脚本定义了一个变量 PLATFORM_LIBS,并根据检测到的平台向其填充不同的库。
1 | # --- 平台检测与配置 --- |
嵌入式 ARM (
if(CMAKE_CROSSCOMPILING)):- 触发条件: 当你在
cmake命令中使用了-DCMAKE_TOOLCHAIN_FILE参数指定交叉编译工具链时,CMAKE_CROSSCOMPILING变量会自动变为TRUE。 - 库配置: 它通过
add_library(freetype_local STATIC IMPORTED)创建了一个名为freetype_local的“导入目标”,并将其指向libs/freetype/lib/arm/libfreetype.a这个预编译好的静态库。最终将pthread,freetype_local和m(数学库) 添加到PLATFORM_LIBS中。
- 触发条件: 当你在
Windows (
elseif(WIN32)):- 触发条件: 在 Windows 系统上编译时自动满足。
- 库配置: 直接指定
libs/目录下预置的 SDL2 和 Freetype 静态库 (.a) 的完整路径,并包含了编译 Windows GUI 程序所需的一系列系统库(如gdi32,winmm等)。
Linux PC (
elseif(UNIX AND NOT APPLE)):- 触发条件: 在 Linux 系统上进行本地编译时满足。
- 库配置: 使用
find_package()在系统中查找SDL2,Threads(线程库) 和Freetype。这要求用户的系统上必须通过包管理器(如sudo apt-get install libsdl2-dev libfreetype-dev)预先安装好这些开发库。
| 目标平台 | 检测方式 | 库来源 | PLATFORM_LIBS 内容 |
|---|---|---|---|
| 嵌入式 ARM | CMAKE_CROSSCOMPILING 为 TRUE | 项目内置的静态库 | pthread, freetype_local, m |
| Windows | WIN32 为 TRUE | 项目内置的静态库 | libSDL2.a, libfreetype.a, 系统库… |
| Linux PC | UNIX AND NOT APPLE | 系统安装的开发库 | SDL2::SDL2, Threads::Threads, … |
3. 自动化核心:动态修改 LVGL 配置文件
这是整个构建脚本最智能的部分。它解决了为不同平台维护不同配置文件的痛点。
1 | # --- 动态修改配置文件 --- |
工作流程:
file(READ ...): 将lv_drv_conf.h和lv_conf.h的全部内容读入 CMake 的字符串变量中。if(TARGET_PLATFORM ...): 根据上一步检测到的平台,进入不同的逻辑分支。string(REGEX REPLACE ...): 使用正则表达式查找特定的宏定义行,例如#define USE_SDL 0或#define USE_SDL 1。(#define[ \t]+USE_SDL[ \t]+): 这是一个捕获组,匹配宏定义的前半部分。[01]: 匹配当前的设置值,无论是 0 还是 1。"\\11": 这是替换内容。\\1是对第一个捕获组的回引(即#define USE_SDL),后面的1是新的值。这样就能精确地将宏的值修改为 1,同时保持原有的格式不变。
file(WRITE ...): 将修改后的字符串内容写回到原始文件中。
效果: 当你为 PC 编译时,脚本会自动将
USE_SDL改为1,USE_FBDEV改为0;而当你交叉编译时,则会自动将USE_SDL改为0,USE_FBDEV改为1。整个过程在cmake配置阶段完成,对开发者完全透明。
4. 源码聚合与编译
1 | include_directories(...) |
include_directories: 将所有需要的头文件路径(如.根目录,UI/,lvgl/)添加到编译器的搜索路径中。file(GLOB_RECURSE ALL_SOURCES ...): 递归地查找所有指定的.c源文件,并将它们的路径列表存入ALL_SOURCES变量中。这避免了手动逐一列出所有源文件的繁琐工作。
5. 生成最终可执行文件
1 | add_executable(${PROJECT_NAME} main.c ... ${ALL_SOURCES}) |
add_executable: 创建最终的可执行文件目标(名为main),并将入口文件main.c以及ALL_SOURCES变量中收集的所有源文件都加入编译。target_link_libraries: 这是最后一步,将可执行文件与之前根据平台配置好的PLATFORM_LIBS变量中的所有库进行链接,生成最终可运行的程序。
通过这些步骤,使这份 CMake 脚本实现了一个高度自动化、可移植且对用户友好的构建环境