CMakeLists.txt基础语法

0. 概览

本节从简单示例开始,逐渐增加其复杂性,来逐渐展示 CMakeLists.txt 的基本语法。

1. 多文件编译

语法:  add_executable(可执行文件 源文件1 源文件2 ... ...)

假设某可执行文件 main 由多个源码文件编译而成:

add_executable(main main.c a.c b.c)

2. 指定头文件路径

语法:  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)

3. 设定变量和调用shell命令

语法:  set(变量名称 变量值)

在指定头文件路径的例子中,当使用绝对路径时,若采用上述方式则在不同电脑间执行会很容易出现错误,更好的方式是让 CMakeLists.txt 在被解析的时候自动获取其所在路径,这可以在 CMakeLists.txt 中嵌套 shell 命令来达到:

# 定义一个变量 SRCDIR,使其值等于当前路径
set(SRCDIR $(pwd))

# 引用变量 SRCDIR 的值来设定头文件所在路径 
include_directories($(SRCDIR)/inc)

4. 添加工程子目录和编译库文件

语法:  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/ 下,这与源码的目录结构保持一致。

5. 指明库路径和链接指定的库文件

语法:  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() 之前。

6. 指定工具链

语法:  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/)

7. 指定编译选项

语法:  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/)

8. 设定cmake的最低版本

语法:  cmake_minimum_required(VERSION x.x)

由于 cmake 的特性会随着版本的更新而发生变化,因此手头写的 CMakeLists.txt 可能在别的电脑不适用,为了检测并提前报告版本兼容性的问题,一般都需要在 CMakeLists.txt 判定 cmake 的版本:

cmake_minimum_required(VERSION 3.8)

需要注意的是,版本检查机制至少在 2.8 之后才被支持,因此上述语句中版本号至少应为 2.8 。

9. 指定工具链文件

语法:  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

10. 设定项目名称

语法:  project(PRO_NAME VERSION x.x.x LANGUAGES X)

一般而言,项目需要设置一个版本号,方便进行版本的发布,也可以根据版本对问题或者特性进行追溯和记录。可以通过如下语句来设定项目的版本信息:

project(cmake_demo VERSION 1.0.0 LANGUAGES C CXX)

11. 常见内置变量

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:重新定义目标二进制可执行文件的存放位置

你可能感兴趣的:(嵌入式常用工具,开源)