本节从简单示例开始,逐渐增加其复杂性,来逐渐展示 CMakeLists.txt 的基本语法。
语法: add_executable(可执行文件 源文件1 源文件2 ... ...)
假设某可执行文件 main 由多个源码文件编译而成:
add_executable(main main.c a.c b.c)
语法: include_directories(头文件所在路径)
假设文件结构如下:
.
├── build/
├── inc/
│ └── head.h
└── main.c
├── CMakeLists.txt
其中,主程序 main.c 依赖于头文件 head.h,那么在与之同目录的 CMakeLists.txt 中,需要以其所在路径为基准添加头文件相对路径或绝对路径:
# CMakeLists.txt
include_directories(./inc) # 相对路径,基于CMakeLists.txt所在路径
add_executable(main main.c)
或者
# CMakeLists.txt
include_directories(/home/gec/cmake/inc) # 绝对路径
add_executable(main main.c)
语法: set(变量名称 变量值)
在指定头文件路径的例子中,当使用绝对路径时,若采用上述方式则在不同电脑间执行会很容易出现错误,更好的方式是让 CMakeLists.txt 在被解析的时候自动获取其所在路径,这可以在 CMakeLists.txt 中嵌套 shell 命令来达到:
# 定义一个变量 SRCDIR,使其值等于当前路径
set(SRCDIR $(pwd))
# 引用变量 SRCDIR 的值来设定头文件所在路径
include_directories($(SRCDIR)/inc)
语法: add_subdirectory(子目录名称) add_library(库名 [库类型] 源文件1 源文件2 ... ...) 库类型:SHARED、STATIC
假设在工程目录中有一个 lib 子目录,里面包含若干源码,需要将其编译成动态库或静态库,目录文件结构如下:
gec@ubuntu:~/cmake$ tree
.
├── build/
├── inc
│ └── head.h
├── lib
│ ├── a.c # 假设要编译成动态库
│ ├── b.c # 假设要编译成静态库
│ └── CMakeLists.txt
├── main.c
└── CMakeLists.txt
其中, a.c 和 b.c 是两个简单模块,分别实现两个整数相加和相减:
// a.c
int sumup(int a, int b)
{
return a+b;
}
// b.c
int minus(int a, int b)
{
return a-b;
}
此时,只需要在 lib/ 下增加一个 CMakeLists.txt 文件即可,其内容如下:
# cmake/lib/CMakeLists.txt
add_library(a SHARED a.c)
add_library(b STATIC b.c) # 静态库是默认的,此处 STATIC 可以不写
注意,上述关键字 SHARED 和 STATIC 必须是大写。然后在顶层 CMakeLists.txt 增加该子目录的包含语句:
# cmake/CMakeLists.txt
add_subdirectory(lib/)
这样,就可以通过顶层的 CMakeLists.txt 来间接执行子目录工程配置信息,最终在 build/ 中生成动态库和静态库。
gec@ubuntu:~/cmake/build$ tree -L 2
.
├── CMakeCache.txt
├── CMakeFiles/
├── cmake_install.cmake
├── lib/ # 此处自动生成的 lib/ 是要与源码目录的 cmake/lib 保持一致
│ ├── CMakeFiles
│ ├── cmake_install.cmake
│ ├── liba.so # 生成的动态库
│ ├── libb.a # 生成的静态库
│ └── Makefile # 可由此 Makefile 重新编译生成库文件
├── main
└── Makefile
值得注意的是:cmake 为了更好地自适应工程文件的结构,其所生成的文件的相对位置均与源码目录中的相对位置保持一致,因此在此处会发现,库文件 liba.so 和 libb.a 均位于 lib/ 下,这与源码的目录结构保持一致。
语法: link_directories(库所在路径) target_link_libraries(目标文件 库名1 库名2 ... ...)
假设主程序 main.c 引用了 lib/ 下各个库文件的接口,则编译时就必须链接相关的库文件。在顶层 CMakeLists.txt 中添加两行 target_link_libraries() 语句:
# CMakeLists.txt
set(SRCDIR $(pwd))
include_directories($(SRCDIR)/inc/)
add_executable(main main.c)
target_link_libraries(main a) # 指明可执行文件main依赖于库a
target_link_libraries(main b) # 指明可执行文件main依赖于库b
add_subdirectory(lib/)
或者
# CMakeLists.txt
set(SRCDIR $(pwd))
include_directories($(SRCDIR)/inc/)
add_executable(main main.c)
target_link_libraries(main a b) # 指明可执行文件main依赖于库a和b
add_subdirectory(lib/)
注意,在以上示例中,并没有指定库文件a和b所在的路径,这是因为这两个库文件所在的路径 lib/ 已经作为子目录被顶层 CMakeLists.txt 添加了,如果主程序所依赖的库文件在别处,比如依赖于 /tmp/libx.so,那么顶层 CMakeLists.txt 要这么写:
# CMakeLists.txt
set(SRCDIR $(pwd))
include_directories($(SRCDIR)/inc/)
link_directories(/tmp)
add_executable(main main.c)
target_link_libraries(main a b)
target_link_libraries(main x) # 指明可执行文件main依赖于库x及其所在路径
add_subdirectory(lib/)
注意:语句 link_directories() 要写在 add_executable() 之前。
语法: set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc") set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++")
假设程序要放到 RockX 平台上去运行,那么需要指定对应平台的交叉工具链:
# CMakeLists.txt
set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc") # 指定C编译器
set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++") # 指定C++编译器
set(SRCDIR $(pwd))
include_directories($(SRCDIR)/inc/)
add_executable(main main.c)
target_link_libraries(main a b)
add_subdirectory(lib/)
语法: set(CMAKE_C_FLAGS "具体编译选项")
编译程序的时候,经常需要增加一些特别的选项,比如增加优化等级、指定运行时链接路径等:
# CMakeLists.txt
set(CMAKE_C_FLAGS "-O2 -Wl,-rpath=./lib") # 设定优化等级和运行库所在路径
set(SRCDIR $(pwd))
include_directories($(SRCDIR)/inc/)
add_executable(main main.c)
target_link_libraries(main a b)
add_subdirectory(lib/)
语法: cmake_minimum_required(VERSION x.x)
由于 cmake 的特性会随着版本的更新而发生变化,因此手头写的 CMakeLists.txt 可能在别的电脑不适用,为了检测并提前报告版本兼容性的问题,一般都需要在 CMakeLists.txt 判定 cmake 的版本:
cmake_minimum_required(VERSION 3.8)
需要注意的是,版本检查机制至少在 2.8 之后才被支持,因此上述语句中版本号至少应为 2.8 。
语法: cmake -DCMAKE_TOOLCHAIN_FILE=xxx
我们可以直接在 CMakeLists.txt 中设置跟工具链相关的所有细节,但有时候工具的设置项比较多,而且同一项目可能要部署到不同的平台中去,不同平台可能对工具链的配置信息不尽相同且都需要保留,那么如果能将跟平台相关的工具链的配置信息单独放在一个文件中,然后在执行 cmake 指令的时候临时指定,将会很大地提升工作效率。
例如,在A平台中,需要设定如下编译配置信息:
# A.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(TOOLCHAIN_DIR /usr/local/arm/)
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/arm-linux-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/arm-linux-g++)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_DIR}
${TOOLCHAIN_DIR}/arm-none-linux-gnueabi/include
${TOOLCHAIN_DIR}/arm-none-linux-gnueabi/lib)
在B平台中,需要设定如下编译配置信息:
# B.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(TOOLCHAIN_DIR /home/gec/RockX/3568)
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/aarch64-linux-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/aaarch64-linux-g++)
那么,可以将这些信息从 CMakeLists.txt 抽离出来,单独形成两个文件:A.cmake 和 B.cmake ,然后在源码编译时通过上述内置命令来灵活指定:
# 选择A
gec@ubuntu:~/cmake/build$ cmake .. -DCMAKE_TOOLCHAIN_FILE=A.make
# 选择B
gec@ubuntu:~/cmake/build$ cmake .. -DCMAKE_TOOLCHAIN_FILE=B.make
语法: project(PRO_NAME VERSION x.x.x LANGUAGES X)
一般而言,项目需要设置一个版本号,方便进行版本的发布,也可以根据版本对问题或者特性进行追溯和记录。可以通过如下语句来设定项目的版本信息:
project(cmake_demo VERSION 1.0.0 LANGUAGES C CXX)
PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行cmake命令的目录,通常为${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置