cmake-examples 的学习笔记,但会根据平时的使用情况酌情有所增删。
1. 基础
1.1 安装
cmake 直接从官网下载 ,有各个平台的预编译包发布。我是在Windows下使用WSL,因为Ubuntu的版本比较老,所以将Linux下最新的TAR包解压到windowns下的Portable目录即可使用。
1.2 运行
CMakeLists.txt 是 cmake 命令
1
2
3
|
cmake_minimum_required(VERSION 3.5)
project (hello_cmake)
add_executable(hello_cmake main.cpp)
|
可以通过如下方式可以运行
1
2
3
|
mkdir build
cd build && cmake ../
make && ../hello_cmake
|
- 建立build的目录的原因是,cmake会生成很多中间的临时文件,避免污染源码目录
- ../ 表示在上一级目录中,查找CMakeLists.txt(文件名是固定,不可修改)文件。
1.3 内置变量
CMake的关于文件的内置变量用于屏蔽各个编译地址的差异,可以根据注释酌情使用。
Variable |
Info |
CMAKE_SOURCE_DIR |
The root source directory |
CMAKE_CURRENT_SOURCE_DIR |
The current source directory if using sub-projects and directories. |
PROJECT_SOURCE_DIR |
The source directory of the current cmake project. |
CMAKE_BINARY_DIR |
The root binary / build directory. This is the directory where you ran the cmake command. |
CMAKE_CURRENT_BINARY_DIR |
The build directory you are currently in. |
PROJECT_BINARY_DIR |
The build directory for the current project. |
1.4 链接库和链接头文件
相当于给GCC添加 -I 选项
1
2
3
|
target_include_directories(${PROJECT_NAME} PRIVATE
${PROJECT_SOURCE_DIR}/include
)
|
生成静态库和使用静态库
1
2
3
4
5
6
|
add_library(hello_library STATIC
src/Hello.cpp
)
target_link_libraries( hello_binary PRIVATE
hello_library
)
|
生成 libhello_library.so 只需要将修饰符变为 SHARED即可,其它不用变
1
2
3
4
5
6
7
|
add_library(hello_library SHARED
src/Hello.cpp
)
add_library(hello::library ALIAS hello_library)
target_link_libraries( hello_binary PRIVATE
hello::library
)
|
修饰符 |
含义 |
INTERFACE |
the directory is added to the include directories for any targets that link this library. |
PRIVATE |
the directory is added to this target’s include directories |
PUBLIC |
As above, it is included in this library and also any targets that link this library. |
1.5 编译类型
指定编译的类型为 Debug 或 Release 模式,一般会用RelWithDebInfo 方便线上有问题时启用GDB调试。
1
|
cmake ../ -DCMAKE_BUILD_TYPE=Release
|
编译选项 |
含义 |
Release |
Adds the -O3 -DNDEBUG flags to the compiler |
Debug |
Adds the -g flag |
MinSizeRel |
Adds -Os -DNDEBUG |
RelWithDebInfo |
Adds -O2 -g -DNDEBUG flags |
cmake 默认没有编译选项,你可以通过如下脚本设置默认的编译选项为RelWithDebInfo
1
2
3
4
5
6
7
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message("Setting build type to 'RelWithDebInfo' as none was specified.")
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
"MinSizeRel" "RelWithDebInfo")
endif()
|
1.6 编译器参数
增加编译器参数的相关选项
1
2
3
|
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)
target_compile_definitions(cmake_examples_compile_flags PRIVATE EX3)
# CMAKE_C_FLAGS & CMAKE_LINKER_FLAGS
|
1.7 选择编译器
可以通过覆盖 cmake 的参数
1
2
3
4
5
|
clang_bin=$(which clang)
clang_cxx_bin=$(which clang++)
#CMAKE_LINKER
cmake .. -DCMAKE_C_COMPILER=$clang_bin \
-DCMAKE_CXX_COMPILER=$clang_cxx_bin
|
还可以设置 CC & CXX 的环境变量
1
2
3
4
|
clang_bin=$(which clang)
clang_cxx_bin=$(which clang++)
export CC=$clang_bin
export CXX=$clang_cxx_bin
|
1.8 编译引擎
cmake 默认生成 Makefile,还可以生成其它引擎的,可以通过 cmake –help 查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Generators
The following generators are available on this platform (* marks default):
* Unix Makefiles = Generates standard UNIX makefiles.
Green Hills MULTI = Generates Green Hills MULTI files
(experimental, work-in-progress).
Ninja = Generates build.ninja files.
Watcom WMake = Generates Watcom WMake makefiles.
CodeBlocks - Ninja = Generates CodeBlocks project files.
CodeBlocks - Unix Makefiles = Generates CodeBlocks project files.
CodeLite - Ninja = Generates CodeLite project files.
CodeLite - Unix Makefiles = Generates CodeLite project files.
Sublime Text 2 - Ninja = Generates Sublime Text 2 project files.
Sublime Text 2 - Unix Makefiles
= Generates Sublime Text 2 project files.
Kate - Ninja = Generates Kate project files.
Kate - Unix Makefiles = Generates Kate project files.
Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files.
Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
|
如下是选用 nijia 的方法
1
|
cmake .. -G Ninja && nijia & nijia install
|
1.9 C 标准
如下是检测并选择C标准的模板
1
2
3
4
5
6
7
8
9
10
11
12
|
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
# check results and add flag
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()
|
设置 CMAKE_CXX_STANDARD 变量,编译系统全局使用。
1
2
3
4
5
|
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED OFF)
message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}")
target_compile_features(hello_cpp11 PUBLIC cxx_auto_type)
|
C++有类似的参数
1
2
3
|
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
|
2. 高级
2.1 函数
CMake通过函数支持模块复用,如find_package就是函数,具体使用如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function (argument_tester arg)
message(STATUS "ARGN: ${ARGN}")
message(STATUS "ARGC: ${ARGC}")
message(STATUS "ARGV: ${ARGV}")
message(STATUS "ARGV0: ${ARGV0}")
list(LENGTH ARGV argv_len)
message(STATUS "length of ARGV: ${argv_len}")
set(i 0)
while( i LESS ${argv_len})
list(GET ARGV ${i} argv_value)
message(STATUS "argv${i}: ${argv_value}")
math(EXPR i "${i} + 1")
endwhile()
endfunction ()
argument_tester(arg0 arg1 arg2 arg3)
|
运行结果
1
2
3
4
5
6
7
8
9
10
|
-- ARGN: arg1;arg2;arg3
-- ARGC: 4
-- ARGV: arg0;arg1;arg2;arg3
-- ARGV0: arg0
-- ARGV1: arg1
-- length of ARGV: 4
-- argv0: arg0
-- argv1: arg1
-- argv2: arg2
-- argv3: arg3
|
ARGC变量表示传递给函数的参数个数。
ARGV0, ARGV1, ARGV2代表传递给函数的实际参数。
ARGN 代表超出最后一个预期参数的参数列表,例如,函数原型声明时,只接受一个参数,那么调用函数时传递给函数的参数列表中,从第二个参数(如果有的话)开始就会保存到 ARGN
1
2
|
target_sources
target_link_libraries("${test_target_name}" leveldb)
|
2.2 宏
宏与函数差不多,就是字符串替换,如下是简单的实例
1
2
3
4
5
6
7
8
9
10
11
12
|
macro(add_test_library name libname)
set(target_name test_${name})
add_executable(
${target_name}
test/test_${name}.cpp
)
target_link_libraries(
${target_name} PRIVATE
${libname}
)
endmacro(add_test_library)
add_test_library(spdlog spdlog)
|
给定宏的名称,参数列表,我们可以遍历宏,链接多个lib
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
macro(add_test_proc name)
set(target_name ${name}_test)
add_executable(
${target_name}
test/${name}_test.cpp
)
#message(STATUS "this is args:${ARGV},ARGN=${ARGN}")
set(libname_list "${ARGN}")
foreach(libname IN LISTS libname_list)
target_link_libraries(
${target_name} PRIVATE
${libname}
)
endforeach()
endmacro(add_test_proc)
add_test_proc(retriever_test Python3::Python cppzmq-static dbg-macro spdlog::spdlog)
|
宏和函数都不支持return ,需要传参出去,可以通过形参输入传出
1
2
3
|
macro(ocv_xxx return_hello_world)
set(return_hello_world "Hello_World")
endmacro()
|
2.3 测试
1
2
|
enable_testing()
add_test(NAME "${test_target_name}" COMMAND "${test_target_name}")
|
2.4 选项
选项参数类似于C++中GFlags参数选项,提供默认值,也可以通过cmake指令中 -D 参数进行覆盖。
1
2
3
|
option(LEVELDB_BUILD_TESTS "Build LevelDB's unit tests" ON)
find_package(Threads REQUIRED)
target_link_libraries(leveldb Threads::Threads)
|
2.5 子工程
主要是 add_subdirectory 指令,作用为添加一个子目录并构建该子目录
1
|
add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])
|
子目录下应该有个CMakefile.txt 文件,实现外部thirdparty的集成,如下是从批量集成的示例 submodules 中摘取出来的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
|
macro(add_test_library name libname)
add_subdirectory(${name} EXCLUDE_FROM_ALL)
set(target_name test_${name})
add_executable(
${target_name}
test/test_${name}.cpp
)
target_link_libraries(
${target_name} PRIVATE
${libname}::${libname}
)
endmacro(add_test_library)
add_test_library(spdlog spdlog)
|
2.6 代码生成
2.6.1 配置文件生成
通过configure_file 指令生成文件
1
2
3
4
5
6
|
set (BUILD_VERSION "3.23.1“)
configure_file(ver.h.in ${PROJECT_BINARY_DIR}/ver.h)
# configure the path.h.in file.
# This file can only use the @VARIABLE@ syntax in the file
configure_file(path.h.in ${PROJECT_BINARY_DIR}/path.h @ONLY)
|
path.in.in & ver.h.in 中可以通过 {} 或 @@ 语法引用CMake的变量,CMake会做字符串替换
1
2
|
const char* ver = "${BUILD_VERSION}";
const char* path = "@CMAKE_SOURCE_DIR@";
|
2.6.2 Protobuf 生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# find the protobuf compiler and libraries
find_package(Protobuf REQUIRED)
# Generate the .h and .cxx files
PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS AddressBook.proto)
# Add an executable
add_executable(protobuf_example
main.cpp
${PROTO_SRCS}
${PROTO_HDRS}
)
target_include_directories(protobuf_example
PUBLIC
${PROTOBUF_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR}
)
# link the exe against the libraries
target_link_libraries(protobuf_example
PUBLIC
${PROTOBUF_LIBRARIES}
)
|
主要是通过 find_package 获取相关的路径。
PROTOBUF_FOUND
- If Protocol Buffers is installed
PROTOBUF_INCLUDE_DIRS
- The protobuf header files
PROTOBUF_LIBRARIES
- The protobuf library
2.6.3 Thrift 生成
Thrift 跟Protobuf一样,具体示例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
add_custom_command(
output ${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_types.h
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_types.cpp
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_constants.h
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_constants.cpp
# 生成异步的客户端
command thrift::thrift -gen cpp:cob_style
-out ${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl
${PROJECT_SOURCE_DIR}/src/proto/xdl.thrift
depends ${PROJECT_SOURCE_DIR}/src/proto/xdl.thrift
)
add_executable(HelloWorldClientThrift
src/HelloWorldClientThrift.cpp
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_types.h
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_types.cpp
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_constants.h
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/xdl_constants.cpp
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl/HelloWorldService.cpp
)
target_link_libraries(HelloWorldClientThrift PRIVATE
thrift::libthriftnb
thrift::libthrift
Threads::Threads
)
target_include_directories(HelloWorldClientThrift PRIVATE
${PROJECT_BINARY_DIR}/gen/thrift/cpp/xdl
)
|
2.7 常用函数
2.7.1 get_filename_component
1
|
get_filename_component(target_name "${CMAKE_CURRENT_SOURCE_DIR}" NAME)
|
赋值到target_name 的变量名中
2.7.2 find_package
2.7.3 add_dependencies
2.7.4 add_custom_command
2.7.5 mark_as_advanced
2.8 include
类似于C语言中include指令,将其它cmake文件“粘贴”到主CMakefile中。具体语法为
1
|
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <VAR>] [NO_POLICY_SCOPE])
|
可以是include其它cmake文件,或者include 其它的model
1
2
3
4
5
|
#file
include(spdlog.cmake)
#Model
include(FetchContent)
include(CMakeParseArguments)
|
2.9 install
install用于指定在安装时运行的规则。它可以用来安装很多内容,可以包括目标二进制、动态库、静态库以及文件、目录、脚本等
1
2
3
4
5
6
|
install(TARGETS <target>... [...])
install({FILES | PROGRAMS} <file>... [...])
install(DIRECTORY <dir>... [...])
install(SCRIPT <file> [...])
install(CODE <code> [...])
install(EXPORT <export-name> [...])
|
1
|
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
|
定义的目标文件,即可执行二进制、动态库、静态库:
目标文件 |
内容 |
安装目录变量 |
默认安装文件夹 |
ARCHIVE |
静态库 |
${CMAKE_INSTALL_LIBDIR} |
lib |
LIBRARY |
动态库 |
${CMAKE_INSTALL_LIBDIR} |
lib |
RUNTIME |
可执行二进制文件 |
${CMAKE_INSTALL_BINDIR} |
bin |
PUBLIC_HEADER |
与库关联的PUBLIC头文件 |
${CMAKE_INSTALL_INCLUDEDIR} |
include |
PRIVATE_HEADER |
与库关联的PRIVATE头文件 |
${CMAKE_INSTALL_INCLUDEDIR} |
include |
3. 杂项