CMake
可以跨平台的生成makefile
,再由make
/ninja
等编译工具根据makefile
的规范进行编译
CMake
根据 CMakeLists.txt
文件执行 指令是大小写无关的
这里以Linux下的使用来介绍CMake
简单实例
一个最简单的Cmake
项目形如
cmake_minimum_required(VERSION 3.15)
# set the project name
project(Tutorial)
# add the executable
add_executable(Tutorial tutorial.cpp)
cmake_minimum_required
指定使用 CMake
的最低版本号
project
指定项目名称
add_executable
用来生成可执行文件,需要指定生成可执行文件的名称和相关源文件(如果有多个,那么就用空格
隔开)。
如果源文件很多,把所有源文件的名字都加进add_executable
将是一件烦人的工作。
更省事的方法是使用 aux_source_directory
命令
aux_source_directory(<dir> <variable>)
该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名
构建使用cmake
使用cmake
cmake <CMakeLists.txt文件存放的目录>
cmake . # CMakeLists.txt文件在当前文件夹底下
编译和链接
cmake --build <生成的文件希望存放的目录>
变量
set(SRC_LIST a.cpp b.cpp c.cpp)
通过set
命令可以指定变量 类似于makefile
的变量
使用时也是类似方法 $
加上用{}
括起的变量名
eg. ${SRC_LIST}
在CMake中,list
命令用于操作列表。它可以用于创建、修改和查询列表,以及将列表转换为字符串。
以下是list
命令的一些常用用法:
-
list(APPEND <list> <element1> [<element2> ...])
:将一个或多个元素添加到列表的末尾。 -
list(INSERT <list> <index> <element1> [<element2> ...])
:将一个或多个元素插入到列表的指定位置。 -
list(REMOVE_ITEM <list> <value1> [<value2> ...])
:从列表中删除一个或多个指定的元素。 -
list(REMOVE_DUPLICATES <list>)
:从列表中删除重复的元素。 -
list(GET <list> <index>)
:获取列表中指定位置的元素。 -
list(LENGTH <list>)
:获取列表的长度。 -
list(SUBLIST <list> <start> [<length>])
:获取列表的子列表。 -
list(JOIN <glue> <list>)
:将列表中的元素用指定的分隔符连接成一个字符串。
以下代码演示了如何使用list命令创建、修改和查询列表:
# 创建一个空列表
set(my_list)
# 向列表中添加元素
list(APPEND my_list "apple" "banana" "orange")
# 在列表的第二个位置插入一个元素
list(INSERT my_list 1 "pear")
# 从列表中删除一个元素
list(REMOVE_ITEM my_list "banana")
# 获取列表的第三个元素
list(GET my_list 2)
# 获取列表的长度
list(LENGTH my_list)
# 获取列表的子列表
list(SUBLIST my_list 1 2)
# 将列表中的元素用逗号连接成一个字符串
list(JOIN "," my_list)
通过SET指令重新定义 EXECUTABLE_OUTPUT_PATH
和LIBRARY_OUTPUT_PATH
变量可以指定最终生成的目标二进制的位置(EXECUTABLE
和LIBRARY
分别对应于两种最终输出的二进制形式)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
输出信息
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message")
向终端输出用户定义的信息,包含三种类型:
SEND_ERROR
产生错误,生成过程被跳过。STATUS
输出前缀为--d
的信息。FATAL_ERROR
立即终止所有的cmake
过程。
添加搜索目录
target_include_directories(${PROJECT_NAME} PUBLIC${PROJECT_BINARY_DIR})
target_include_directories
命令可以将需要添加的头文件目录加入搜索路径
添加子目录
使用命令 add_subdirectory
指明本项目包含一个子目录, 这样该子目录下的CMakeLists.txt
文件和源代码也会被处理。
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录, 并可以指定中间二进制和目标二进制存
放的位置。
其中,source_dir
是要添加的子目录的路径,binary_dir
是要生成二进制文件的路径(默认为source_dir
),EXCLUDE_FROM_ALL
表示是否将该子目录排除在所有目标之外(默认为不排除),使用时直接写上这个变量就行,eg. add_subdirectory(my_library EXCLUDE_FROM_ALL)
使用add_subdirectory命令向CMake项目中添加一个子目录:
# CMakeLists.txt
# 添加一个子目录
add_subdirectory(my_library)
# 在当前目录中定义一个可执行文件
add_executable(my_app main.cpp)
# 将子目录中的库添加到可执行文件中
target_link_libraries(my_app my_library)
使用链接库
ADD_LIBRARY(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
SHARED
, 动态库STATIC
, 静态库MODULE
,在使用dyld
的系统有效,如果不支持dyld
,则被当做SHARED
对待。EXCLUDE_FROM_ALL
参数的意思是这个不会被默认构建,除非有其他的组件依赖或者手工构建。
使用命令 add_library
将 src
目录中的源文件编译为静态链接库:
aux_source_directory(. DIR_LIB_SRCS)
add_library (MathFunctions ${DIR_LIB_SRCS})
使用命令 target_link_libraries 指明可执行文件需要连接一个链接库
target_link_libraries(my_test MathFunctions)
配置头文件
configure_file
命令用于加入一个配置头文件
configure_file (
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
这里配置头文件 config.h
由 CMake
从 config.h.in
生成,通过这样的机制,可以通过预定义一些参数和变量来控制代码的生成。
自定义编译选项
option
命令可以添加一个选项,并且设置默认值
option (USE_MYMATH "Use provided math implementation" ON)
这里添加了一个 USE_MYMATH
选项,并且默认值为ON
接着在config.h.in
文件中使用该选项:
#cmakedefine USE_MYMATH
这样 CMake 会自动根据 CMakeLists
配置文件中的设置自动生成 config.h
文件
添加库的使用要求
INTERFACE
是指消费者需要、但生产者不需要的那些东西
使用INTERFACE
可以指定使用要求 使得任何使用该库的文件自动包含该路径
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
除了 INTERFACE
,还有PRIVATE
和 PUBLIC
。
INTERFACE
表示消费者需要生产者不需要
PRIVATE
表示消费者不需要生产者需要
PUBLIC
表示消费者和生产者都需要。
如何安装
通过在bash中调用如下指令可以安装cmake目标文件:
cmake -D CMAKE_INSTALL_PREFIX=/usr .
在安装前,需要在CMakeLists
中定义好如何安装
INSTALL
指令包含了各种类型,我们需要一个个分开解释
目标文件的安装
INSTALL(TARGETS targets ...
[
[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions ...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
]
[...])
TARGETS后面跟的就是我们通过ADD_EXECUTABLE
或者ADD_LIBRARY
定义的目标文
件,可能是可执行二进制、动态库、静态库。
目标类型:ARCHIVE
特指静态库,LIBRARY
特指动态库,RUNTIME
特指可执行目标二
进制。
DESTINATION
定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候
CMAKE_INSTALL_PREFIX
其实就无效了。
如果你希望使用CMAKE_INSTALL_PREFIX
来定义安装路径,就要写成相对路径,不要以/
开头
安装后的路径就是 ${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
这里将二进制文件myrun
安装到${CMAKE_INSTALL_PREFIX}/bin
目录
将动态库mylib
安装到 ${CMAKE_INSTALL_PREFIX}/lib
目录
将静态库mystaticlib
安装到 ${CMAKE_INSTALL_PREFIX}/libstatic
目录。
为工程添加测试
添加测试同样很简单。CMake
提供了一个称为 CTest
的测试工具。CTest
是CMake
的一个附加模块,它提供了一组工具和命令,用于自动化测试和测试结果的收集和报告。我们要做的只是在项目根目录的 CMakeLists
文件中调用一系列的 add_test
命令。
以下是使用CTest添加测试的一般步骤:
-
在
CMakeLists.txt
文件中添加enable_testing()
命令,以启用CTest
。 -
使用
add_test()
命令添加测试。该命令需要两个参数:测试名称
和测试命令
。测试命令
可以是任何可执行文件或脚本,用于运行测试。 -
使用
add_executable()
命令定义测试可执行文件。测试可执行文件应该包含测试代码,并输出测试结果。 -
使用
target_link_libraries()
命令将测试可执行文件链接到需要测试的库或可执行文件。
使用CTest添加测试:
# CMakeLists.txt
# 启用CTest
enable_testing()
# 添加一个测试 测试程序是否成功运行
add_test(test_run my_test 5 2)
# 测试帮助信息是否可以正常提示
add_test(test_usage my_test)
set_tests_properties(test_usage
PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")
# 测试 5 的平方
add_test(test_5_2 my_test 5 2)
set_tests_properties(test_5_2
PROPERTIES PASS_REGULAR_EXPRESSION "is 25")
# 测试 10 的 5 次方
add_test(test_10_5 my_test 10 5)
set_tests_properties(test_10_5
PROPERTIES PASS_REGULAR_EXPRESSION "is 100000")
# 测试 2 的 10 次方
add_test (test_2_10 my_test 2 10)
set_tests_properties(test_2_10
PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")
# 定义测试可执行文件
add_executable(my_test my_test.cpp)
# 将测试可执行文件链接到需要测试的库或可执行文件
target_link_libraries(my_test my_library)
test_run
用来测试程序是否成功运行并返回 0 值
使用set_tests_properties()
命令设置了测试属性,将PASS_REGULAR_EXPRESSION
属性设置为一个正则表达式,用于匹配测试输出中的帮助信息是否包含后面跟着的字符串。
我们可以使用marco
命令定义一个宏来简化测试
# 定义一个宏,用来简化测试工作
macro (do_test arg1 arg2 result)
add_test(test_${arg1}_${arg2} my_test ${arg1} ${arg2})
set_tests_properties (test_${arg1}_${arg2}
PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
# 使用该宏进行一系列的数据测试
do_test(5 2 "is 25")
do_test (10 5 "is 100000")
do_test (2 10 "is 1024")
这段代码定义了一个名为do_test
的宏,用于添加测试和设置测试属性。该宏接受三个参数:arg1
、arg2
和result
,分别表示测试的输入参数和期望输出结果。
在宏的实现中,我们首先使用add_test()
命令添加一个名为test_${arg1}_${arg2}
的测试,并指定测试命令为my_test
,输入参数为${arg1}
和${arg2}
。然后,我们使用set_tests_properties()
命令设置测试属性,将PASS_REGULAR_EXPRESSION
属性设置为${result}
,用于匹配测试输出中的期望结果。
使用add_custom_target()
命令可以添加自定义测试目标,如代码覆盖率测试、性能测试等。自定义测试目标是一种特殊的目标,它不会生成任何实际的输出文件,而是用于运行测试命令和收集测试结果。
代码覆盖率是一种衡量软件测试质量的指标,用于评估测试是否覆盖了被测试代码的所有执行路径。它通常用百分比表示,表示被测试代码中被测试的部分占总代码量的比例。
常见的代码覆盖率指标包括:
-
语句覆盖率(Statement Coverage):测试覆盖了被测试代码中的所有语句的比例。
-
分支覆盖率(Branch Coverage):测试覆盖了被测试代码中的所有分支的比例。
-
函数覆盖率(Function Coverage):测试覆盖了被测试代码中的所有函数的比例。
-
条件覆盖率(Condition Coverage):测试覆盖了被测试代码中的所有条件的比例。
-
路径覆盖率(Path Coverage):测试覆盖了被测试代码中的所有执行路径的比例。
以下是使用add_custom_target()
命令添加自定义测试目标的一般步骤:
-
使用
add_custom_target()
命令添加自定义测试目标。该命令需要两个参数:目标名称和测试命令。测试命令可以是任何可执行文件或脚本,用于运行测试。 -
使用
add_dependencies()
命令将自定义测试目标添加到CTest的测试目标中。这样,当我们运行ctest命令时,CTest会自动运行自定义测试目标,并将测试结果收集到CTest的测试报告中。
# CMakeLists.txt
# 添加一个自定义测试目标,用于运行代码覆盖率测试
add_custom_target(coverage
COMMAND lcov --directory . --capture --output-file coverage.info
COMMAND genhtml coverage.info --output-directory coverage-report
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Generating code coverage report"
)
# 将自定义测试目标添加到CTest的测试目标中
add_dependencies(test coverage)
在这个例子中,我们使用add_custom_target()
命令添加一个名为coverage
的自定义测试目标,并指定测试命令为运行代码覆盖率测试的命令。
这个命令使用lcov
工具和genhtml
工具生成代码覆盖率报告,并将报告输出到coverage-report
目录中。我们还使用WORKING_DIRECTORY
选项指定工作目录为CMake
的二进制目录,以便正确地生成测试报告。
最后,我们使用add_dependencies()
命令将自定义测试目标coverage
添加到CTest
的测试目标test
中,以便CTest
能够自动运行代码覆盖率测试,并将测试结果收集到测试报告中。
支持 gdb
让 CMake 支持 gdb 的设置也很容易,只需要指定 Debug 模式下开启 -g
选项:
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
这段代码设置了CMake的构建类型为Debug
,并分别设置了CMAKE_CXX_FLAGS_DEBUG
和CMAKE_CXX_FLAGS_RELEASE
变量,用于指定不同构建类型下的编译选项。
在Debug
构建类型下,我们设置了以下编译选项:
-
$ENV{CXXFLAGS}
:从环境变量CXXFLAGS
中获取其他编译选项。 -
-O0
:关闭优化,以便在调试时能够更好地查看代码。 -
-Wall
:开启所有警告,以便在编译时能够发现潜在的问题。 -
-g
:生成调试信息,以便在调试时能够更好地查看代码。 -
-ggdb
:生成gdb专属调试信息,以便在使用gdb调试器时能够更好地查看代码。
在Release
构建类型下,我们设置了以下编译选项:
-
$ENV{CXXFLAGS}
:从环境变量CXXFLAGS
中获取其他编译选项。 -
-O3
:开启最高级别的优化,以便在发布时能够获得最佳的性能。 -
-
Wall
:开启所有警告,以便在编译时能够发现潜在的问题。
这些编译选项可以帮助我们在不同的构建类型下编译和调试代码,并优化代码的性能和质量。在实际使用中,我们可以根据具体的项目需求和编译环境来调整这些编译选项,并使用其他选项和工具来进一步优化代码的性能和质量。
生成安装包
CPack
,它同样也是由 CMake 提供的一个工具,专门用于打包。首先在顶层的CMakeLists.txt
文件尾部添加下面几行:
#构建一个CPack安装包
include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE
"${CMAKE_CURRENT_SOURCE_DIR}/License. txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}" )
set(CPACK_PACKAGE__VERSION_MINOR "${Demo_VERSION_MINOR}")
include(CPack)
这段代码使用了CPack
模块来生成软件包,并设置了一些软件包相关的属性。
首先,我们使用include(InstallRequiredSystemLibraries)
命令来包含InstallRequiredSystemLibraries
模块,以便在安装软件包时自动安装所需的系统库。
然后,我们使用set()
命令设置了以下软件包属性:
-
CPACK_RESOURCE_FILE_LICENSE
:指定软件包的许可证文件路径。 -
CPACK_PACKAGE_VERSION_MAJOR
:指定软件包的主版本号,通常表示重大更新或功能变化。 -
CPACK_PACKAGE_VERSION_MINOR
:指定软件包的次版本号,通常表示小的更新或修复。
最后,我们使用include(CPack)
命令包含CPack
模块,以便生成软件包。CPack
模块提供了许多选项和变量,可以帮助我们生成各种类型的软件包,如ZIP、TGZ、RPM、DEB等。我们可以根据具体的需求和环境来调整这些选项和变量,以生成符合要求的软件包。
请注意,这段代码中的变量${Demo_VERSION_MAJOR}
和${Demo_VERSION_MINOR}
是由configure_file()
命令生成的,用于指定软件包的版本号。在实际使用中,我们需要根据具体的项目需求和版本管理策略来设置软件包的版本号,并使用适当的选项和工具来生成和发布软件包。
接着在bash下调用cpack
即可生成安装包
生成二进制安装包
cpack -C CPackConfig.cmake
生成源码安装包
cpack -C CPackSourceConfig.cmake
-C
选项是CPack
命令的一个选项,用于指定CPack
的配置文件。该选项后面需要跟一个配置文件的路径或名称,以便CPack
使用该配置文件来生成软件包。
例如,cpack -C CPackConfig.cmake
命令将使用名为CPackConfig.cmake
的配置文件来生成软件包。该配置文件通常包含了软件包的属性、组件、文件列表、安装目录等信息,以便根据这些信息来生成符合要求的软件包。
请注意,-C
选项是CPack
命令的一个必需选项,如果没有指定该选项,CPack
将无法生成软件包。
其他一些常用的选项:
-
-G <generator>
:指定要使用的生成器,如ZIP、TGZ、RPM、DEB等。可以使用cpack --help
命令查看可用的生成器列表。 -
-B <build-dir>
:指定生成软件包的临时构建目录。 -
-R <regex>
:指定一个正则表达式,用于选择要打包的文件和目录。 -
-D <variable>=<value>
:设置CPack变量的值,用于覆盖CMakeLists.txt文件中的默认设置。 -
-V
:显示详细的CPack生成过程和输出信息。 -
--config <config-file>
:指定CPack的配置文件。 -
--verbose
:显示更详细的输出信息。 -
--help
:显示CPack的帮助信息。