CMake通过CMakelist进行项目构建,它是一个跨平台的工具,只需要一份CMakelist就可以在全平台构建项目,CMake会控制对应构建系统(如Makefile或Ninja)完成构建,非常方便。
本文以Clion新建项目生成的cmakelist和开源项目RT-Thread所构建的cmakelist做为示例进行介绍。
单文件cmakelist
以下是一个cmakelist的示例:
cmake_minimum_required(VERSION 3.28)
project(4_2 C)
set(CMAKE_C_STANDARD 11)
add_executable(4_2 main.c)
第一行cmake_minimum_required:构建项目所需的最低cmake版本
第二行project:4_2是项目的名字,C是指定的语言,也可以不指定 like this:project(4_2)
,如果不指定,会默认支持C语言。(显式指定语言可以避免混淆)
第三行set:设置c的版本(这一行可以不写,则编译器会使用默认版本)
第四行add_executable:4_2
是项目需要构建的可执行文件,由main.c编译而成
多文件cmakelist
如果你用到三个c文件,你可以这样写:
cmake_minimum_required(VERSION 3.28)
project(7_1 C)
set(CMAKE_C_STANDARD 11)
add_executable(7_1 main.c
wheel.c
wheel.h
sorts.c
sorts.h)
这样会把main.c和其他几个c文件一起编译进可执行文件中。
等等,这里出现了问题,我们知道h文件是编译时编译器会自动寻找的,也就是说,只需要这样就可以了:
add_executable(7_1 main.c
wheel.c
sorts.c
)
不管包不包含h文件,构建都会正常进行,包含h文件在语法上是支持的。
进阶 CMake语法
实际项目中所使用的.c文件远远不止于此,因此,掌握一些cmake基本语法对大工程的构建至关重要。
INCLUDE_DIRECTORIES()
项目中用到的头文件应该被INCLUDE_DIRECTORIES()
这个函数所包含。包含的是.h文件存在的那个文件夹。文件夹与文件夹之间可以只用空格分开,也可以换行使阅读更清晰。
INCLUDE_DIRECTORIES(
.
applications
board
board/CubeMX_Config/Inc
packages/CMSIS-Core-latest/Include
)
在上文我们刚说可以不用指定头文件位置,为什么在此又用对应的函数呢?
当你在源文件中使用#include "header.h"
或#include <header.h>
时,编译器需要知道header.h
文件的具体位置,如果头文件位于当前源文件目录之外,编译器需要额外的搜索路径来找到这些头文件。如果不添加路径,可能会有找不到h文件的报错。
此外,在大型项目中,头文件可能分布在多个目录中。通过include_directories()
或target_include_directories()
,可以清晰地指定这些路径,使项目结构更加灵活和可维护。
ADD_DEFINITIONS()
ADD_DEFINITIONS()
命令用于向编译器添加预定义的宏(-D
选项),
ADD_DEFINITIONS(
-DSTM32F103xB
-DUSE_HAL_DRIVER
-D__RTTHREAD__
-DRT_USING_NEWLIBC
-DRT_USING_LIBC
-D_POSIX_C_SOURCE=1
)
如果使用gcc
作为编译器,CMake会生成类似以下的命令行:
gcc -DSTMD32F103xB -DUSE_HAL_DRIVER -D__RTTHREAD__ -DRT_USING_NEWLIBC -DRT_USING_LIBC -D_POSIX_C_SOURCE=1 -c main.c -o main.o
SET()
SET()
可以把一组C源文件的路径存储到变量中
RT_FINSH_SOURCES
存储的就是后面几行c文件
SET(RT_FINSH_SOURCES
../../../components/finsh/cmd.c
../../../components/finsh/shell.c
../../../components/finsh/msh_parse.c
../../../components/finsh/msh.c
)
本质是把变量名设置一个值,如:
SET(MY_VARIABLE "Hello, World!")
SET(ENABLE_DEBUG TRUE)
ADD_LIBRARY()
ADD_LIBRARY()
是 CMake 中的一个核心命令,用于创建一个库目标(library target)
ADD_LIBRARY(rtt_Finsh OBJECT ${RT_FINSH_SOURCES})
rtt_Finsh
:是对象库的名称
OBJECT
:这个关键字指定了要创建的对象库。它告诉 CMake 不要生成一个独立的库文件,而是将源文件编译成对象文件
${RT_FINSH_SOURCES}
:RT_FINSH_SOURCES
所存储的c文件会被编译成对象文件,并存储在对象库 rtt_Finsh
中
此外,if语句也是支持的
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake)
endif()
此外,CMake命令的名称在语法上是大小写不敏感的,但变量名称是大小写敏感的。
因此你可以看到这样的写法:CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
也可以看到这样的写法:cmake_minimum_required(VERSION 3.10)
这二者是一样的
What else
cmake还可以跟kconfig连用,实现更自动化的项目构建,比如RT-Thread就使用menuconfig进行项目配置,生成.config文件,使用Scons命令根据.config、sconscript文件控制cmakelist的生成,将menuconfig配置后不需要编译的文件剔除出去,完成裁剪。最后用cmakelist完成项目构建。