1 cmake 简介
cmake通过读取脚本文件CMakeLists.txt来构建编译系统。
cmake使用CMakeLists.txt中的规则来生成Makefile文件,最终通过make生成可执行程序或库文件,但是cmake的语法比Makefile简单得多。
2 cmake 安装
sudo apt install cmake build-essiential
3 cmake 规则
1、cmake默认使用CMakeLists.txt作为脚本文件构建编译系统。就像make默认使用Makefile一样;
2、在项目顶层CMakeLists.txt的第一行为cmake_minimum_required(VERSION 3.5),指定了cmake的最小版本。因为在一些功能在低版本的cmake中是没有的,比如target_link_libraries()在3.13版本才开始支持;
3、使用out of source方式构建系统。由于cmake在编译过程中会产生很多中间文件,所以一般会单独新建build文件夹来存储cmake产生的中间文件;
4、cmake对变量大小写敏感,使用关键字set定义和设置变量值,通过${varname}的方式使用变量。
4 cmake 中的“hello world”
首先,书写CMakeLists.txt,为cmake提供编译规则和依赖
cmake_minimum_required(VERSION 3.5) # cmake最小版本要求
add_executable(helloworld helloworld.cpp) # 可执行文件名 源文件名
然后,新建build文件夹用来存储cmake的中间文件
cd your_workspace # 进入工作空间
mkdir build # 新建build文件夹
cd build # 进入build文件夹
cmake .. # 在上层目录执行cmake(CMakeLists.txt在上层目录中)
make # 根据产生的Makefile执行make
最后,运行build中产生的可执行文件helloworld
./helloworld # 运行可执行文件
5 一个简单的小工程
假设工程目录如下
|----CMakeLists.txt
|----main.cpp
|----hello
|----CMakeLists.txt
|----hello.cpp
|----hello.h
|----world
|----CMakeLists.txt
|----world.cpp
|----world.h
# 工程文件有hello和world两个子目录
# main.cpp调用hello/hello.cpp和world/world.cpp中的函数
整个工程文件体现的是模块化思想,在hello和world两个子目录中也有CMakeLists.txt文件。当然可以将hello.cpp/.h和world.cpp/.h文件直接放在工程文件目录下,只使用一个CMakeList.txt,但是这样比较混乱(其实大部分开源工程都是直接放在工程文件目录下的...)
现在看一下这3个CMakeLists.txt文件:
@ ./ CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(helloworld) # 项目名称,可通过${PROJECT_NAME}来访问
add_subdirectory(hello) # 子目录subdir,必须包含CMakeLists.txt
add_subdirectory(world)
include_directories(hello world)
# aux_source_directory(dir var) 将dir下的所有源文件赋值给变量var,后续通过${var}来访问
aux_source_directory(. MAIN_SRC_FILES)
add_executable(${PROJECT_NAME} ${MAIN_SRC_FILES})
# target_link_libraries(target lib) 将库文件lib链接到可执行文件(或库文件)target
# 库文件lib可以通过subdir中CMakeLists.txt的add_library()生成,或是第三方库如Eigen、Sophus
target_link_libraries(${PROJECT_NAME} hello)
target_link_libraries(${PROJECT_NAME} world)
@ ./hello/CMakeLists.txt
project(hello)
aux_source_directory(. HELLO_SRC_FILES)
# add_library(target srcs) 指定源文件srcs生成目标文件target,目标文件是一个静态库
# add_library(target SAHRED srcs) 生成动态库(默认)
# add_library(target STATIC srcs) 生成静态库
add_library(${PROJECT_NAME} ${HELLO_SRC_FILES})
@ ./world/CMakeLists.txt
project(world)
aux_source_directory(. WORLD_SRC_FILES)
add_library(${PROJECT_NAME} ${WORLD_SRC_FILES})
可以注意到,在3个CMakeLists.txt文件里都有project(name),这是遵循就近原则的,都可以通过${PROJECT_NAME}访问。
6 add_subdirectory()命令
add_sybdirectory()多用于添加下级目录。但是在有些情况下,多个下级目录之间可能会相互调用甚至会对上级目录调用。所以像 5 中简单地使用add_sybdirectory()是不对的,需要使用第二个参数--binary_dir,具体语法如下:
# 生成的subdirectory目标文件存储在binary_dir指定的目录中
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
假设另外一种目录结构,test、hello、world同级
|----CMakeLists.txt
|----main.cpp
|----test
|----CMakeLists.txt
|----test.cpp
|----hello
|----CMakeLists.txt
|----hello.cpp
|----hello.h
|----world
|----CMakeLists.txt
|----world.cpp
|----world.h
hello/:生成libhello.so 动态库
world/:生成libworld.so 动态库
test/:测试上述两个库是否可用
现在我们要书写test/CMakeLists.txt文件,既然test要调用hello和world中的库,那么必然要使用include_directories()命令和add_subdirectory()命令,使得test包括hello和world,但是简单地使用add_subdirectory()是不对的,必须要使用第二参数--binary_dir。那么为啥 5 可以直接简单使用add_subdirectory()呢?是因为 5 中是上级目录包含下级目录hello和world。
以下是正确的 test/CMakeLists.txt文件书写形式
cmake_minmun_required(VERSIOIN 3.5)
project(test)
set(TOP_DIR ../) # 赋值
include_directories(${TOP_DIR}/hello ${TOP_DIR}/world)
# 错误写法,test与hello和world是同级的
# add_subdirectory(${TOP_DIR}/hello)
# add_subdirectory(${TOP_DIR}/hello)
# 正确写法,将subdir生成的库文件*.so,存储在build/*_binary_dir文件中
add_subdirectory(${TOP_DIR}/hello hello_binary_dir)
add_subdirectory(${TOP_DIR}/hello world_binary_dir)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} hello)
target_link_libraries(${PROJECT_NAME} world)
其实还有另外一种写法,就是不强求使用add_subdirectory(),毕竟test与hello和world是同级关系,可以在顶级目录./CMakeLists.txt中使用add_subdirectory(test)生成test中的可执行程序,同时将test/CMakeLists.txt中的两个add_subdirectory()去掉,只保留include_directories()。
7 指定库和可执行程序的生成目录
根据 6,正常编译后,在build/中生成了许多文件,这些文件都是以各个PROJECT_NAME命名的,整个build/包含
./build/
|----CMakeCache.txt
|----CMakeFiles
|----cmake_install.cmake
|----Makefile
|----helloworld # 可执行文件
|----test/
|----CMakeFiles
|----cmake_install.cmake
|----Makefile
|----test # 可执行文件
|----hello/
|----CMakeFiles
|----cmake_install.cmake
|----Makefile
|----libhello.so # hello动态库
|----world/
|----CMakeFiles
|----cmake_install.cmake
|----Makefile
|----libworld.so # world动态库
当然,我们可以手动设置可执行文件、动态库和静态库的位置(假设hello生成动态库,world生成静态库)
cmake_minimum_required(VERSION 3.5)
project(helloworld)
# CMAKE_LIBRARY_OUTPUT_DIRECTORY 动态库目录
# CMAKE_ARCHIVE_OUTPUT_DIRECTORY 静态库目录
# CMAKE_RUNTIME_OUTPUT_DIRECTORY 可执行文件目录
# CMAKE_SOURCE_DIR 主目录
# CMAKE_CURRENT_LIST_DIR 当前CMakeLists.txt所在目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/output/lib-so)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/output/lib-ar)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/output/bin)
add_subdirectory(test)
add_subdirectory(hello)
add_subdirectory(world)
include_directories(hello world)
aux_source_directory(. MAIN_SRC_FILES)
add_executable(${PROJECT_NAME} ${MAIN_SRC_FILES})
target_link_libraries(${PROJECT_NAME} hello)
target_link_libraries(${PROJECT_NAME} world)
正常编译后,产生目录如下
./
|----build/
|---- ...
|----output/
|----bin/
|----helloworld
|----test
|----lib-ar
|----libworld.a
|----lib-so
|----libhello.so
8 编译可视化技巧
消息打印,可测试变量名称是否正确
message(STATUS "variable value: ${var-name}")
显示具体编译过程,比如链接了哪些库文件、指定了哪些头文件路径等。有两种方法:
1)在CMakeLists.txt中设置
set(CMAKE_VERBOSE_MAKEFILE ON)
2)make编译时,使用make指令后缀
make VERBOSE=1