本博客是在 CMake 保姆级教程 文章的学习上进行的总结,把常用CMake命令总结如下。
CMake 使用 #
进行行注释,使用 #[[ ]]
形式进行块注释。
# 行注释
#[[
块注释
]]
以下三个命令是使用CMake构建最简单的一个项目所需要的命令。
cmake_minimum_required
:指定使用的 CMake 的最低版本;
project
:定义工程名称,并可指定工程的版本、工程描述等;
add_executable
:将指定的源码编译生成为一个可执行程序。
cmake_minimum_required(VERSION 3.0)
project(CALC)
add_executable(app add.c div.c main.c mult.c sub.c)
指定使用的 C++ 标准,set(CMAKE_CXX_STANDARD 11)
set
定义变量在 CMake 中,变量的值始终是字符串类型,且区分大小写。
set(
设置一个普通变量,
表示变量名,
表示一个或多个参数,例如:
# 将 add.c div.c main.c mult.c sub.c 存储到 SRC 变量中
set(SRC add.c div.c main.c mult.c sub.c)
# 取变量 SRC 的值
add_executable(app ${SRC})
使用 ${
的形式取变量的值。
程序的输出可能是一个可执行文件、可能是制作的共享库,可以通过以下命令设置指定的输出路径。
# 将输出路径指定为 /xxx/xxx
set(EXECUTABLE_OUTPUT_PATH /xxx/xxx)
# or
set(RUNTIME_OUTPUT_DIRECTORY /xxx/xxx)
EXECUTABLE_OUTPUT_PATH
和 RUNTIME_OUTPUT_DIRECTORY
是预定义的宏,当设置了 RUNTIME_OUTPUT_DIRECTORY
时,EXECUTABLE_OUTPUT_PATH
就失效了。
在 CMake 中,可以使用 aux_source_directory
命令或者 file
命令 搜索项目中的头文件或源文件。
aux_source_directory(
将
目录下的源文件存储到
变量中。
file
命令具有很多功能,这里介绍搜索文件的功能。
file(GLOB
file(GLOB_RECURSE
搜索的表达式,可以是指定目录,指定目录下的某种文件,理解为一种正则表达式就好了;GLOB
非递归搜索;GLOB_RECURSE
递归搜索;
搜索结果保存的变量名
上面时 CMake 文档中给出的命令示例,常用的形式如下:
# 非递归搜索 /xxx/xxx 下的 *.cpp 源文件
file(GLOB SRC /xxx/xxx/*.cpp)
# 递归搜索 /xxx/xxx 下的所有 *.h 头文件
file(GLOB_RECURSE HEADER /xxx/xxx/*.h)
指定源文件对应的头文件,include_directories(/xxx/include)
,源文件对应的头文件指定在 /xxx/include
目录下。
以如下示例进行实验。
.
├── CMakeLists.txt
├── build
├── include
│ └── head.h
├── main.cpp
└── src
├── add.cpp
├── divi.cpp
├── mul.cpp
└── sub.cpp
CMakeLists.txt
内容如下
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
# 制作静态库的命令
add_library(calc STATIC ${SRC_LIST})
步骤:
cd build
切换到 build 目录下,build 目录用来存放 cmake 生成的 makefile 以及其他一些文件;cmake ..
命令,即根据 CMakeLists.txt 文件生成 makefile 文件;set(LIBRARY_OUTPUT_PATH /xxx/xxx)
命令设置输出路径。LIBRARY_OUTPUT_PATH
宏用来指定构建的共享库的存放路径。Linux 下生成的静态库默认是不具有执行权限的,如下所示。因此在指定静态库生成的路径时,不能使用 EXECUTABLE_OUTPUT_PATH
和 RUNTIME_OUTPUT_DIRECTORY
宏。
$ ls -l libcalc.a
-rw-r--r-- 1 root root 5254 Dec 3 18:04 libcalc.a
CMakeLists.txt
内容如下:
cmake_minimum_required(VERSION 3.0)
project(CALC)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
add_library(calc SHARED ${SRC_LIST})
和制作静态库的步骤相同。
同时生成动态库和静态库
当需要同时生成动态库和静态库时,不能如下所示简单的将上述的命令拼在一起,这样会导致静态库和动态库名字相同的冲突。
cmake_minimum_required(VERSION 3.0)
project(calc)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
add_library(${PROJECT_NAME} SHARED ${SRC_LIST})
# 生成静态库
add_library(${PROJECT_NAME} STATIC ${SRC_LIST})
# output:
# CMake Error at CMakeLists.txt:10 (add_library):
# add_library cannot create target "calc" because another target with the
# same name already exists. The existing target is a shared library created
# in source directory "/workspace/cppCode/demo03". See documentation for
# policy CMP0002 for more details.
一种解决方案是,使用 set_target_properties
命令修改最终生成的静态库和动态库的名字,如下所示。
cmake_minimum_required(VERSION 3.0)
project(calc)
include_directories(${PROJECT_SOURCE_DIR}/include)
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
add_library(${PROJECT_NAME}_shared SHARED ${SRC_LIST})
# 生成静态库
add_library(${PROJECT_NAME}_static STATIC ${SRC_LIST})
# 修改生成的动态库和静态库的名称
set_target_properties(${PROJECT_NAME}_shared PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
set_target_properties(${PROJECT_NAME}_static PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
将目标名称 target1
的 prop1
属性设置为 value1
;将目标名称 target2
的 prop2
属性设置为 value2
;
以上述制作的静态库和动态库为例,程序代码结构如下所示:
.
├── CMakeLists.txt
├── build
├── include
│ └── head.h
├── lib
│ ├── libcalc.a
│ └── libcalc.so
├── main.cpp
链接静态库需要的命令如下:
# 指定静态库的路径, 表示静态库的路径
link_directories()
# 链接静态库的命令, 表示静态库的名称,可以是全名 libxxx.a,或掐头去尾 xxx
link_libraries( [...])
CMakeLists.txt 内容如下,编译通过。
cmake_minimum_required(VERSION 3.0)
project(calc)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定静态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
# 链接静态库
link_libraries(calc)
add_executable(main main.cpp)
链接动态库使用 target_link_libraries
命令指定动态库,和链接静态库相似,直接看 CMakeLists.txt 内容:
cmake_minimum_required(VERSION 3.0)
project(calc)
include_directories(${PROJECT_SOURCE_DIR}/include)
# 指定动态库的路径
link_directories(${PROJECT_SOURCE_DIR}/lib)
add_executable(main main.cpp)
# 链接动态库
target_link_libraries(main calc)
需要注意的一个点是,链接动态库的命令 target_link_libraries
需要在 add_executable
命令后面,理由很简单:如上示例所示,add_executable(main main.cpp)
只有“生成”了 main 可执行文件之后,才能去加载动态库。
在一个复杂的项目中,需要对不同功能的代码进行划分,且对不同功能的代码编译条件不同,一个常见的例子是,源代码需要编译动态库,而测试代码需要编译为可执行文件。针对这类情况,一种解决方案是使用嵌套的CMakeLists.txt。以下面的示例为例。
模拟一个项目的目录结构如下所示:
.
├── cppproject
│ ├── CMakeLists.txt
│ ├── add
│ │ └── add.cpp
│ ├── include
│ │ └── calc.h
│ ├── sub
│ │ └── sub.cpp
│ └── test
│ ├── CMakeLists.txt
│ ├── test_add.cpp
│ └── test_sub.cpp
├── docs
├── images
└── readme.md
程序源文件在 cppproject
目录下,假设该程序有两个划分的功能,源码实现分别在 add 和 sub 目录下,include 是头文件所在目录,测试 add 和 sub 功能的源文件在 test 目录下。现在需要将 add 和 sub 两个功能编译为一个静态库和动态库,将 test 目录下的测试源码编译为可执行程序,下面将展示如何使用嵌套 CMake 实现上述要求的编译。
在展示具体的 CMakeLists.txt 前,先简单说明一下嵌套CMake的实现逻辑。CMake通过 CMakeLists.txt 文件来标识一个 CMake 节点,CMake 节点之间形成层级关系,以上面的示例为例,cppproject 所在的目录下为父节点(其目录下有一个 CMakeLists.txt 文件),test 所在目录为 cppproject 的子节点(test 目录下有一个 CMakeLists.txt 文件)。需要注意,这种层级关系不是通过目录之间的层级关系来实现的,而是通过 add_subdirectory
命令来实现的。下面直接看对应的 CMakeLists.txt 文件的内容。
cppproject/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(cppproject)
set(CMAKE_CXX_STANDARD 11)
# 设置生成库文件的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 设置可执行文件的路径,这里的目的为:将test目录下的测试源程序编译生成为可执行程序,并输出到该指定目录下
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin)
# 设置头文件路径
set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 设置静态库和动态库的名字
set(CALC_LIB calc)
# 设置可执行测试程序的名字
# 也可以不在这里设置,在test目录中的 CMakeLists.txt 文件中设置
set(TEST_ADD test_add)
set(TEST_SUB test_sub)
# 添加子目录,即含有 CMakeLists.txt 文件的子目录
add_subdirectory(test)
aux_source_directory(add SRC)
aux_source_directory(sub SRC)
# include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB}_static STATIC ${SRC})
add_library(${CALC_LIB}_shared SHARED ${SRC})
# 修改生成的静态库和动态库的名字
set_target_properties(${CALC_LIB}_static PROPERTIES OUTPUT_NAME ${CALC_LIB})
set_target_properties(${CALC_LIB}_shared PROPERTIES OUTPUT_NAME ${CALC_LIB})
cppproject/test/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(test)
set(CMAKE_CXX_STANDARD 11)
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
link_directories(${LIB_PATH})
# 使用CMake父节点中设置的变量名作为生成的可执行程序的名称
add_executable(TEST_ADD test_add.cpp)
add_executable(TEST_SUB test_sub.cpp)
target_link_libraries(test_add calc)
target_link_libraries(test_sub calc)
从上面示例中的两个 CMakeLists.txt 文件可以看出,对于CMake父节点,其通过 add_subdirectory(test) 命令将 CMake 子节点关联起来。在父节点中,设置了一些编译过程中需要的变量,设置了动态库、静态库的编译选项;而在子节点中,设置了编译生成可执行程序的一些命令。在嵌套的CMake中,CMake中定义的变量的作用关系为:
当嵌套的CMakeLists.txt文件编写完成后,其编译命令的过程和单CMakeLists.txt文件的编译过程一致。编译过程直接截图如下:
本文梳理总结了使用CMake构建项目的常用命令,但对于一个复杂的项目,可能还会涉及将生成的静态库或动态库拷贝复制到系统的指定目录下、多系统的条件编译等复杂设置,多用多查自然就熟悉了。
https://cmake.org/cmake/help/latest/
https://subingwen.cn/cmake/CMake-primer/
https://subingwen.cn/cmake/CMake-advanced/