0. 前言
Step 1 - A Basic Starting Point
1.1. 基本功能
cmake 的基本功能就是三个命令,下面简单介绍。 cmake_minimum_required(VERSION 3.10)
project(Tutorial)
作用:提供项目名称、版本、使用编译语言等信息 官方文档
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[HOMEPAGE_URL <url-string>]
[LANGUAGES <language-name>...])
add_executable(Toturial tutorial.cxx)
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
[source1] [source2 ...])
1.2. 添加版本号
设置版本好就是在 project
命中中指定 VERSION
。 值得一提的是,在设置过VERSION
属性后,几个cmake变量会自动定义:
PROJECT_VERSION, <PROJECT-NAME>_VERSION
PROJECT_VERSION_MAJOR, <PROJECT-NAME>_VERSION_MAJOR
PROJECT_VERSION_MINOR, <PROJECT-NAME>_VERSION_MINOR
PROJECT_VERSION_PATCH, <PROJECT-NAME>_VERSION_PATCH
PROJECT_VERSION_TWEAK, <PROJECT-NAME>_VERSION_TWEAK.
之后,可通过 configure_file
命令,将版本号写入对应的头文件中。具体方法分为三步
第一步:定义模版文件,即本例中的 TutorialConfig.h.in
,如下。 第二步:通过 configure_file(TutorialConfig.h.in TutorialConfig.h)
将模版中的参数转换为 PROJECT
命令中的VERSION。
configure_file
的功能就是将模版文件所有 @VAR@
转换为cmake参数 ${VAR}
下面源码中的 @Tutorial_VERSION_MAJOR@
就是之前cmake自动定义的变量。 第三步:将生成的 TutorialConfig.h
添加到项目中,即target_include_directories(Tutorial PUBLIC "${PROJECT_BINARY_DIR}")
target_include_directories
:将头文件目录添加到一个target中
// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
1.3. 设置C++标准
设置C++版本的本质就是指定若干cmake参数
CMAKE_CXX_STANDARD
& CMAKE_CXX_STANDARD_REQUIRED
# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
Step 2 - Adding a Library
2.1. 基本功能
目标:添加第三方库。
这个第三方库 指的是源码(需要编译),而不是动态库、静态库。 基本流程:
第一步:准备好第三方库的源码,并在第三方项目中建立 CMakeLists.txt。
CMakeLists.txt
文件中添加 add_library()
指令。 第二步:在原始项目 CMakeLists.txt 文件中关联第三方项目以及添加头文件。 项目根目录的 CMakeLists.txt
中添加依赖有以下几个内容
通过 add_subdirectory
添加第三方库路径,这样才会编译、构建第三方项目。 通过 target_link_libraries
添加库的名称。这个名称就是在第三方库的add_library
中定义的。 通过 target_include_directories
添加第三方库的头文件。
2.2. 其他功能
本例中实现了一些其他功能:
有两个版本的sqrt实现,一个是C++标准库,一个是我们自己实现的第三方库。 在CMake中采用了一个option
来指定sqrt的来源。 为了实现这个功能,需要在 CMakeLists.txt
文件以及C++源码进行一些修改。 CMakeLists.txt 文件中
# 添加选项,用来选择是否使用自定义第三方库
option(USE_MYMATH "Use tutorial provided math implementation" ON)
# 通过条件语句,选择是否将第三方库添加到项目中
if(USE_MYMATH)
add_subdirectory(MathFunctions)
list(APPEND EXTRA_LIBS MathFunctions)
list(APPEND EXTRA_INCLUDES "${PROJECT_SOURCE_DIR}/MathFunctions")
endif()
C++ 源码中,使用 #ifdef USE_MYMATH #endif
来指定使用的第三方库。
Step 3 - Adding Usage Requirements for Library
Usage requirements
不知道该怎么翻译。直译就是“使用要求”。 看后面的内容,大概意思就是在为target添加各类内容(如头文件、libs)的同时,做一些其他工作。 主要影响的命令有:
target_compile_definitions()
target_compile_options()
target_include_directories()
target_link_libraries()
下面举个例子:
一个假设:对于所有要添加的libs,都要添加对应的头文件所在路径。 上一个sample中存在的问题:需要分别手动添加libs与头文件目录。 修改第三方库的CMakeLists.txt文件
通过INTERFACE,为第三方库的target指定头文件。
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
)
Step 4 - Installing and Testing
功能:添加 install 以及 test 相关内容
4.1. install
本质就是将文件复制/移动到目标位置。 本例中就是将动态库so文件与头文件移动到对应的 lib 以及 include 文件夹中
install 命令官方文档 实现的功能:
将第三方库中的so文件放到lib目录下,头文件放到include目录下。 将根目录中头文件放到include目录下,可执行文件放到bin目录下。 其中,DESTINATION
指定的 lib/include/bin 目录,如果是绝对路径则直接使用,如果是相对路径则要与 CMAKE_INSTALL_PREFIX
配合使用。
# 第三方库,末尾添加
install(TARGETS MathFunctions DESTINATION lib)
install(FILES MathFunctions.h DESTINATION include)
# 根目录,末尾添加
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
DESTINATION include
)
4.2. Testing
简单说,就是测试构建出来的可执行文件。 主要实现方式就是 add_test
命令
add_test(NAME <name> COMMAND <command> [<arg>...]
[CONFIGURATIONS <config>...]
[WORKING_DIRECTORY <dir>]
[COMMAND_EXPAND_LISTS])
在根目录CMakeLists.txt后添加以下内容
重新构建项目后,可通过 ctest
命令进行测试,如 ctest -N
判断一共有那几个测试,ctest -VV
执行测试并输出过程结果。 下面有的测试是判断程序是否能够正常运行,有的判断输出结果。
enable_testing()
# does the application run
add_test(NAME Runs COMMAND Tutorial 25)
# does the usage message work?
add_test(NAME Usage COMMAND Tutorial)
set_tests_properties(Usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage:.*number"
)
# define a function to simplify adding tests
function(do_test target arg result)
add_test(NAME Comp${arg} COMMAND ${target} ${arg})
set_tests_properties(Comp${arg}
PROPERTIES PASS_REGULAR_EXPRESSION ${result}
)
endfunction(do_test)
# do a bunch of result based tests
do_test(Tutorial 4 "4 is 2")
do_test(Tutorial 9 "9 is 3")
do_test(Tutorial 5 "5 is 2.236")
do_test(Tutorial 7 "7 is 2.645")
do_test(Tutorial 25 "25 is 5")
do_test(Tutorial -25 "-25 is [-nan|nan|0]")
do_test(Tutorial 0.0001 "0.0001 is 0.01")
Step 5 - Adding System Introspection
5.1. 基本实现
主要命令:CheckSymbolExists
格式:check_symbol_exists(<symbol> <files> <variable>)
功能:判断symbol在files中是否存在,将结果保存到variable中。 CMakeLists.txt 中的修改
# 根目录下添加
# 注意,要放到 add_subdirectory(MathFunctions) 后,configure_file(TutorialConfig.h.in TutorialConfig.h)前
include(CheckSymbolExists)
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(NOT (HAVE_LOG AND HAVE_EXP))
unset(HAVE_LOG CACHE)
unset(HAVE_EXP CACHE)
set(CMAKE_REQUIRED_LIBRARIES "m")
check_symbol_exists(log "math.h" HAVE_LOG)
check_symbol_exists(exp "math.h" HAVE_EXP)
if(HAVE_LOG AND HAVE_EXP)
target_link_libraries(MathFunctions PRIVATE m)
endif()
endif()
之后通过 #cmakedefine HAVE_EXP
来定义宏。
5.2. 进阶实现
前一种方法不太方便:
需要通过 #cmakedefine
在头文件中定义宏 需要在原文件中引入头文件 建议使用 target_compile_definitions
target_compile_definitions(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
# add compile definitions
if(HAVE_LOG AND HAVE_EXP)
target_compile_definitions(MathFunctions
PRIVATE "HAVE_LOG" "HAVE_EXP")
endif()
6. Adding a Custom Command and Generated File
目标:通过自定义命令行生成文件。 实现细节:
编写C++程序生成文件。 通过配置cmake命令,先编译上述c++程序,并通过命令行调用该程序生成文件。 本例中,生成的是一个头文件,所以需要修改cmake将该头文件导入到项目中。 要实现上述功能,主要就是修改第三方库的 CMakeLists.txt。
# 生成命令,并调用该命令生成文件
add_executable(MakeTable MakeTable.cxx)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
DEPENDS MakeTable
)
# 将上述文件导入项目中
add_library(MathFunctions
mysqrt.cxx
${CMAKE_CURRENT_BINARY_DIR}/Table.h
)
target_include_directories(MathFunctions
INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
)
主要命令就是 add_custom_command
add_custom_command(OUTPUT output1 [output2 ...]
COMMAND command1 [ARGS] [args1...]
[COMMAND command2 [ARGS] [args2...] ...]
[MAIN_DEPENDENCY depend]
[DEPENDS [depends...]]
[BYPRODUCTS [files...]]
[IMPLICIT_DEPENDS <lang1> depend1
[<lang2> depend2] ...]
[WORKING_DIRECTORY dir]
[COMMENT comment]
[DEPFILE depfile]
[JOB_POOL job_pool]
[VERBATIM] [APPEND] [USES_TERMINAL]
[COMMAND_EXPAND_LISTS])