一,CMake简介
CMake的全称是Cross-platform Make。我第一次参与Linux C 开发时使用的工具是Make,而后开始切换到CMake,一开始以为CMake是和C语言有关,原来开头的C表示它可以跨平台。
CMake的使用场景:
跨平台编译运行,交叉编译。一般基于CMakeLists.txt文件定义的编译构建规则来生成目标文件和目标库。
CMakeLists.txt样例如下:
代码语言:javascript复制#cmake最低版本需求
cmake_minimum_required(VERSION 3.13)
#项目名称
project(cmake_study)
#相关设置用set函数
set(CMAKE_CXX_STANDARD 11)
#生成的可执行文件的名称
add_executable(cmake_study src/main.cc)
在Linux环境使用CMake的构建和编译流程如下:
step1. 编写CMake的配置文件——CMakeLists.txt。
step2. 执行命令 cmake PATH 或者 ccmake PATH 构建生成 Makefile配置文件。PATH为CMakeLists.txt所在的目录。
step3. 在Makefile文件所在的路径,执行make命令进行编译。
一般使用过程如下:
代码语言:javascript复制$ mkdir build
$ cd build/
$ cmake ..
$ make
* 为了不让编译产生的中间文件污染项目的文件结构,专门创建build文件夹进行编译构建。
二,CMake与Make的区别
CMake并不直接参与软件的构建和编译,而是生成用于构建的Makefile等配置文件。因此在完成同样的编译任务时,CMake比Make的用法更容易,且屏蔽了Makefile中的很多复杂的语法点。
三,CMakeLists.txt语法
cmake的语法由函数名和参数构成,参数区分大小写,函数名不区分大小写(这个依据个人喜好,笔者习惯用小写,大写有点费眼睛 _ )。
(1) cmake_minimum_required
含义:设置项目所需的最低cmake版本以及更新策略
语法:
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
使用样例:
cmake_minimum_required(VERSION 2.8.0)
(2) project
含义:设置项目的名称、版本、编程语言等信息
语法:
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[LANGUAGES <language-name>...])
使用样例:
project(Demo)
(3) set
含义:设置普通变量、缓存或环境变量的值
语法:
set(<variable> <value>... [PARENT_SCOPE])
set(<variable> <value>... CACHE <type> <docstring> [FORCE])
set(ENV{<variable>} [<value>])
使用样例:
set(CMAKE_CXX_COMPILER D:/MinGW/bin/g )
(4) file
含义:定义对文件系统的文件和路径的操作,可以结合Linux指令对文件的操作去理解。
语法:
file(READ <filename> <out-var> [...])
file({WRITE | APPEND} <filename> <content>...)
file(MAKE_DIRECTORY [<dir>...])
使用样例:
file(WRITE test.txt "Test Writen" )
(5) option
含义:提供用户可以选择的布尔选项。
语法:
option(<variable> "<help_text>" [value])
使用样例:
option(TEST_DEBUG "option for debug" OFF)
(6) if…else[if]…endif
含义:这个不用详细介绍了,用法同编程语言中的控制语句
语法:
if/else([<condition>])
使用样例:
if(WIN32)
message(STATUS "in Windows System")
elseif(UNIX)
message(STATUS "in Unix System")
endif()
(7) include_directories
含义:将指定目录添加到编译器的头文件搜索范围
语法:
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
使用样例:
include_directories(../src/com/include)
(8) link_directories
含义:添加需要链接的共享库(动态链接库)文件路径,相当于g 命令的-L参数,也相当于Linux环境变量设置LD_LIBRARY_PATH
语法:
link_directories([AFTER|BEFORE] directory1 [directory2 ...])
使用样例:
link_directories(${SOURCE_DIR}/lib)
(9) aux_source_directory
含义:查找指定目录中的所有源文件,将结果存进指定变量名
语法:
aux_source_directory(<dir> <variable>)
使用样例:
aux_source_directory(../src DIR_SRCS)
(10) add_custom_command
含义:添加自定义构建规则
语法:
add_custom_command(OUTPUT output1 [output2 ...]
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...])
使用样例:
add_custom_command(
TARGET ${_target}
POST_BUILD
COMMAND echo ${_command}
VERBATIM)
(11) add_compile_options
含义:设置编译选项
语法:
add_compile_options(<option> ...)
使用样例:
add_compile_options(-std=c 11)
(12) add_subdirectory
含义:将子目录添加到构建范围
语法:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
使用样例:
add_subdirectory(utils)
(13) add_executable
含义:使用指定的源文件来生成目标可执行文件
语法:
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
使用样例:
add_executable(main main.cpp)
(14) add_dependencies
含义:给编译目标添加依赖的target
语法:
add_dependencies(<target> [<target-dependency>]...)
使用样例:
add_dependencies(log com_log)
(15) add_library
含义:添加一个库到工程中,指定这个库的源文件
语法:
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[<source>...])
使用样例:
add_library(opencv_core SHARED IMPORTED)
(16) configure_file
含义:将文件复制到另一个位置并修改其内容。
语法:
configure_file(<input> <output>
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
FILE_PERMISSIONS <permissions>...])
使用样例:
configure_file(CMakeLists.txt.in download/CMakeLists.txt)
(17) find_package
含义:查找依赖的包名
语法:
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]])
使用样例:
find_package(OpenSSL REQUIRED)
(18) find_library
含义:查找依赖的库
语法:
find_library (<VAR> name1 [path1 path2 ...])
使用样例:
find_library(LOG_LIB log)
(19) find_path
含义:搜索包含指定文件名的路径
语法:
find_path (<VAR> name1 [path1 path2 ...])
使用样例:
find_path(_ZeroMQ_ROOT NAMES include/zmq.h)
(20) target_link_libraries
含义:将之前打包的库链接到生成的目标上
语法:
target_link_libraries(<target> ... <item>... ...)
使用样例:
target_link_libraries(${THREAD_LIB_NAME} pthread)
(21) target_include_directories
含义:指定编译生成目标时,需要使用的目录
语法:
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
使用样例:
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include)
(22) target_sources
含义:指定构建目标或其依赖项时要使用的源文件
语法:
target_sources(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
使用样例:
target_sources(main PRIVATE main.cpp)
(23) target_compile_definitions
含义:在编译目标文件时,指定要用到的编译选项
语法:
target_compile_definitions(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
使用样例:
target_compile_definitions(${PROJECTNAME} PUBLIC ARM7)
(24) message
含义:编译过程添加日志消息
语法:
message([<mode>] "message text" ...)
使用样例:
message(STATUS "sources into a library? ${LIBRARY}")
四,CMake常用的环境变量
--CMAKE_C_COMPILER
指定C编译器
--CMAKE_CXX_COMPILER
指定C 编译器
--CMAKE_BUILD_TYPE
指定构建类型,例如Debug, Release
--CMAKE_C_FLAGS
指定C编译器配置
--CMAKE_CXX_FLAGS
指定C 编译器配置
--CMAKE_INSTALL_PREFIX
指定安装的路径前缀
--CMAKE_EXE_LINKER_FLAGS
创建可执行文件时,定义链接器的配置
--CMAKE_MODULE_LINKER_FLAGS
创建模块时,定义链接器的配置
--CMAKE_BINARY_DIR
构建树顶层的完整路径
--PROJECT_BINARY_DIR
构建项目的完整路径
--CMAKE_SOURCE_DIR
源代码树顶层的完整路径
--PROJECT_SOURCE_DIR
当前项目的顶级源目录
--CMAKE_CURRENT_SOURCE_DIR
cmake 当前正在处理的源目录的完整路径
--EXECUTABLE_OUTPUT_PATH
生成的可执行文件路径
--LIBRARY_OUTPUT_PATH
生成的库路径
--BUILD_SHARED_LIBS
通过add_library构建“STATIC/SHARED”库
--CMAKE_CURRENT_LIST_FILE
当前正在处理的文件列表的完整路径
--CMAKE_CURRENT_LIST_LINE
当前正在处理的文件的行号
--CMAKE_MODULE_PATH
提供find_package搜索第三方库时使用的路径
五,开发场景中常见的CMakeList样例
场景一,简单应用
代码语言:javascript复制cmake_minimum_required(VERSION 3.1...3.24)
#项目声明:项目名/版本号/编码语言
project(
ModernCMakeExample
VERSION 1.0
LANGUAGES C )
#把源代码添加进构建的目标库
add_library(MyLibExample simple_lib.cpp simple_lib.hpp)
#生成可执行文件
add_executable(MyExample simple_example.cpp)
#设置链接生成的库文件的名称
target_link_libraries(MyExample PRIVATE MyLibExample)
场景二,复杂工程--基于开源项目libjsonutils
代码语言:javascript复制cmake_minimum_required(VERSION 3.13...3.19 FATAL_ERROR)
project(libjsonutils VERSION 1.0.0 LANGUAGES CXX)
#Make sure that custom modules like FindRapidJSON are found
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake)
# Find system dependencies
set(MIN_BOOST_VERSION 1.65)
find_package(Boost ${MIN_BOOST_VERSION} REQUIRED COMPONENTS regex)
set(MIN_RapidJSON_VERSION 1.1)
find_package(RapidJSON ${MIN_RapidJSON_VERSION} REQUIRED)
# Create target and set properties
add_library(jsonutils
src/json_utils.cpp
src/file_utils.h
)
#Add an alias so that library can be used inside the build tree, e.g. when testing
add_library(JSONUtils::jsonutils ALIAS jsonutils)
#Set target properties
target_include_directories(jsonutils
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_compile_features(jsonutils PRIVATE cxx_auto_type)
target_compile_options(jsonutils PRIVATE
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
-Wall -Wextra -Wpedantic>)
target_link_libraries(jsonutils
PUBLIC
Boost::headers RapidJSON::RapidJSON
PRIVATE
Boost::regex
)
场景三,交叉编译,嵌入式场景用的比较多,通过编写toolchain.cmake指定编译时的工具链
toolchain.cmake样例
代码语言:javascript复制#设定目标操作系统的名称
set(CMAKE_SYSTEM_NAME Windows)
#设定编译器
set(CMAKE_CXX_COMPILER i686-w64-mingw32-g )
#调整find命令的运行模式:在目标环境中搜索头文件和库
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
#在宿主机环境中搜索程序
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
学习CMake时,直接对着语法看最枯燥且收获最小。CMake由于足够灵活,带来的问题就是晦涩难懂,笔者发现身边很多经验丰富的开发者在编写CMakeList.txt时一样头疼。所以不能指望像学习脚本语言一样看完一遍便可熟悉,而是应该像查字典一样在开发中学习。可以把一些开源项目下载到自己的编译环境,查看项目中的CMakeList的写法,然后尝试自己编译和修改,可以加深对CMake用法的理解。
参考教程:
《CMake Cookbook》
https://www.hahack.com/codes/cmake/
https://doc.embedfire.com/linux/
https://github.com/Kitware/CMake
https://github.com/pabloariasal/modern-cmake-sample