相关资料整理:
原仓库:https://github.com/dev-cafe/cmake-cookbook
陈晓伟对CMake-Cookbook的中文翻译:https://github.com/xiaoweiChen/CMake-Cookbook
CMake-Cookbook中文目录
官方文档: https://cmake.org/cmake/help/v3.12/manual/cmake-buildsystem.7.html#object-libraries
写在前面,常用的一些cmake命令
一、编译项目
mkdir build
cd build
cmake ..
二、构建项目生成可执行文件
cmake --build . --config Release
备注:等同于Cmakelists.txt中set(CMAKE_BUILD_TYPE Release CACHE STRING “Build type” FORCE)。在build时,利用config设置编译类型。
三、安装
设置安装路径并执行安装命令
cmake -DCMAKE_INSTALL_PREFIX=E:\cmakeoutput ..
cmake --build . --target install --config Release
```bash
CMAKE_SOURCE_DIR: 顶级cmakelists.txt的文件夹目录。
CMAKE_BINRAY_DIR: 对应cmake的build的目录,主要是运行时生成的文件目录。
CMAKE_CURRENT_SOURCE_DIR: 一般来说,一个工程会有多个cmakelists.txt文件,对应当前文件目录。
CMAKE_CURRENT_BINARY_DIR: 对应build里的目录。
CMAKE_MODULE_PATH: api(include/find_package)包含别的cmake文件时的搜索目录。
CMAKE_PREFIX_PATH: api(find_libray/path)包含模块时的搜索目录。
CMAKE_INSTALL_PREFIX: 调用install相关函数,要生成/保存的根目录路径。
PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR是等价的。也就是当前源码的目录
CMAKE_SIZEOF_VOID_P 空指针的大小 EQUAL 8表示64位 EQUAL 4表示32位
CMAKE_HOST_SYSTEM_PROCESSOR 运行CMake的CPU的名称
变量
project(recipe-04 VERSION 2.0.1 LANGUAGES C)
PROJECT_VERSION_MAJOR #2
PROJECT_VERSION_MINOR #0
PROJECT_VERSION_PATCH #1
PROJECT_VERSION #cmake中的版本号变量
CMAKE_CXX_COMPILER_ID 编译器ID
CMAKE_SYSTEM_NAME 当前系统名字
BUILD_SHARED_LIB 是一个全局变量,主要是用于控制cmake是否可以生成动态so
语法
set 设置参数,包含一般/缓存/环境变量。
unset 取消设置参数
EXISTS #判断文件是否存在
list 针对列表操作,比如针对文件列表/参数列表/编译列表的增加删除这些。
list(APPEND _sources Message.hpp Message.cpp) 向_source变量添加源文件
string 针对字符串的操作,如大小写,查找,正则表达式匹配等。
message 打印消息,可以跟踪测试修改。
add_compile_options:不同平台一般来说有不同的编译设置。
add_definitions:添加预处理器定义。
include_directories: 如visual studio里的,头文件搜索目录,在当前项目以及当前项目用add_subdirectory添加的项目都会应用。
target_sources(helloworld xxx.cpp)#向依赖库中添加源文件
target_include_directories:针对指定目标的include_directories。
link_libraries: 添加库文件路径,注意是全路径,如果是本方案的项目,直接使用项目名就行。在当前项目以及当前项目用add_subdirectory添加的项目都会应用。
target_link_libraries:指定目标的link_directories。
add_library:添加库,根据参数生成静态或是动态库。
add_executable:添加执行文件。
set_target_properties:指定项目一些具体编辑器里的属性,如生成lib/dll的目录。
target_compile_definitions: 添加预处理器定义
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Configuring on/for Linux")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
message(STATUS "Configuring on/for Windows")
configure_file 可将cmake中的变量值传到.h.in文件变量中,然后生成转换成.h文件
configure_file(foo.h.in foo.h @ONLY) cmake-cookbook 2章4节
find_program # This command is used to find a program.
file:
file 针对文件操作,如收集文件列表,读写文件等。
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/helloworld.cpp helloworld) #读取当前目录下的helloworld.cpp文件为helloworld变量
file(TO_NATIVE_PATH ${CMAKE_INSTALL_PREFIX}/${INSTALL_${p}DIR} _path )
TO_NATIVE_PATH模式将cmake样式 转换为带有特定于平台的斜杠的本机路径(\在Windows和/或其他地方)。
语法:
# 字符串中添加变量
target_compile_definitions(hello-world PUBLIC "COMPILER_NAME=\"${CMAKE_CXX_COMPILER_ID}\"")
# 如果`MPI_FOUND`为真,那么` $<BOOL:${MPI_FOUND}>`的值将为1
$<$<BOOL:${MPI_FOUND}>:MPI::MPI_CXX>
foreach( )
endforeach()
其中< items >是用分号或空格分隔的项列表。foreach和匹配的endforeach之间的所有命令都被记录,而没有被调用。一旦endforeach被求值,记录的命令列表将为< items >中的每个项调用一次。在每次迭代开始时,变量< loop_var >将被设置为当前项的值。`
1. 使用`add_custom_command`编译目标,生成输出文件。
2. `add_custom_target`的执行没有输出。
3. 构建目标前后,`add_custom_command`的执行可以没有输出。
add_custom_target添加一个具有给定名称的目标,以执行给定命令。目标没有输出文件,即使命令试图创建具有目标名称的文件,也始终被视为过期。使用add_custom_command()命令生成具有依赖项的文件。默认情况下,任何内容都不依赖于自定义目标。使用add_dependencies()命令向其他目标或从其他目标添加依赖项。
add_custom_target(unpack-eigen
ALL
COMMAND
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen-5a0156e40feb.tar.gz
COMMAND
${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
COMMENT
"Unpacking Eigen3 in ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4"
)
add_dependencies(linear-algebra unpack-eigen)#指定可执行目标对自定义目标的依赖关系
add_custom_command(
OUTPUT
${wrap_BLAS_LAPACK_sources}
COMMAND
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
COMMAND
${CMAKE_COMMAND} -E touch ${wrap_BLAS_LAPACK_sources}
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
COMMENT
"Unpacking C++ wrappers for BLAS/LAPACK"
VERBATIM
)
include(CMakePrintHelpers)
cmake_print_properties(
TARGETS MPI::MPI_CXX
PROPERTIES INTERFACE_LINK_LIBRARIES
)
此模块帮助设置.lib .dll .pdb文件的输出路径
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
类似于cmd命令之类,whoami是返回系统用户名
execute_process(COMMAND <cmd1> [<arguments>]
[COMMAND <cmd2> [<arguments>]]...
[WORKING_DIRECTORY <directory>]
[TIMEOUT <seconds>]
[RESULT_VARIABLE <variable>]
[RESULTS_VARIABLE <variable>]
[OUTPUT_VARIABLE <variable>]
[ERROR_VARIABLE <variable>]
[INPUT_FILE <file>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[COMMAND_ECHO <where>]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[ENCODING <name>]
[ECHO_OUTPUT_VARIABLE]
[ECHO_ERROR_VARIABLE]
[COMMAND_ERROR_IS_FATAL <ANY|LAST>])
cmake中获取git信息
execute_process( # 执行一个子进程
COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%h # 命令
OUTPUT_VARIABLE ${_git_hash} # 输出字符串存入变量
OUTPUT_STRIP_TRAILING_WHITESPACE # 删除字符串尾的换行符
ERROR_QUIET # 对执行错误静默
WORKING_DIRECTORY # 执行路径
${CMAKE_CURRENT_SOURCE_DIR}
)
(1)${ARGC}保存给定宏的所有参数数量。
(2)${ARGV} 保存给定宏的所有参数列表。
(3)${ARGN}保存超过最后一个预期参数的参数列表,即额外参数列表。
macro(宏名称 参数1 参数2 ...)
...
endmacro()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(colors) #通过include引用colors.cmake
cmake_parse_arguments模块官方解释
function(add_catch_test)
set(options)
set(oneValueArgs NAME COST)
set(multiValueArgs LABELS DEPENDS REFERENCE_FILES)
cmake_parse_arguments(add_catch_test
"${options}"
"${oneValueArgs}"
"${multiValueArgs}"
${ARGN}
)
message(STATUS "defining a test ...")
message(STATUS " NAME: ${add_catch_test_NAME}")
message(STATUS " LABELS: ${add_catch_test_LABELS}")
message(STATUS " COST: ${add_catch_test_COST}")
message(STATUS " REFERENCE_FILES: ${add_catch_test_REFERENCE_FILES}")
CMake | include_guard命令详解
cmakecookb-10.1
设置依赖库的属性:
set_target_properties(message-shared
PROPERTIES
POSITION_INDEPENDENT_CODE 1
SOVERSION ${PROJECT_VERSION_MAJOR}
OUTPUT_NAME "message"
DEBUG_POSTFIX "_d"
PUBLIC_HEADER "Message.hpp"
MACOSX_RPATH ON
WINDOWS_EXPORT_ALL_SYMBOLS ON
)
POSITION_INDEPENDENT_CODE 1:设置生成位置无关代码所需的编译器标志。
SOVERSION ${PROJECT_VERSION_MAJOR} : 这是动态库提供的应用程序编程接口(API)版本。在设置语义版本之后,将其设置为与项目的主版本一致。
DEBUG_POSTFIX “_d”:这告诉CMake,如果我们以Debug配置构建项目,则将_d后缀添加到生成的动态库。
PUBLIC_HEADER “Message.hpp”:我们使用这个属性来设置头文件列表(本例中只有一个头文件),声明提供的API函数。这主要用于macOS上的动态库目标,也可以用于其他操作系统和目标
MACOSX_RPATH ON:这将动态库的install_name部分(目录)设置为macOS上的@rpath。
WINDOWS_EXPORT_ALL_SYMBOLS ON:这将强制在Windows上编译以导出所有符号。
在C++、Python混合编程中。Python模块在${CMAKE_CURRENT_BINARY_DIR}
中创建,为了让Python的test.py
脚本找到它,我们使用一个自定义环境变量传递相关的路径,该环境变量用于在test.py
中设置path
变量。请注意,如何将命令设置为调用CMake可执行文件本身,以便在执行Python脚本之前设置本地环境。这为我们提供了平台独立性,并避免了环境污染:
cmake-cookbook 9.6
add_test(
NAME
python_test
COMMAND
${CMAKE_COMMAND} -E env ACCOUNT_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR}
ACCOUNT_HEADER_FILE=${CMAKE_CURRENT_SOURCE_DIR}/account/account.h
ACCOUNT_LIBRARY_FILE=$<TARGET_FILE:account>
${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/account/test.py
)
cookbook-10.1
add_test(
NAME test_shared
COMMAND $<TARGET_FILE:hello-world_wDSO>
)
GenerateExportHeader可规定动态库只公开最小的符号,从而限制代码中定义的对象和函数对外的可见性。我们希望在默认情况下,动态库定义的所有符号都对外隐藏。
include(GenerateExportHeader)
generate_export_header(account
BASE_NAME account
)
cmakecookbook-10.2
include(GenerateExportHeader)
generate_export_header(message-shared
BASE_NAME "message"
EXPORT_MACRO_NAME "message_EXPORT"
EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
DEPRECATED_MACRO_NAME "message_DEPRECATED"
NO_EXPORT_MACRO_NAME "message_NO_EXPORT"
STATIC_DEFINE "message_STATIC_DEFINE"
NO_DEPRECATED_MACRO_NAME "message_NO_DEPRECATED"
DEFINE_NO_DEPRECATED
)
设置可执行文件的属性:
set_target_properties(hello-world_wDSO
PROPERTIES
MACOSX_RPATH ON
SKIP_BUILD_RPATH OFF
BUILD_WITH_INSTALL_RPATH OFF
INSTALL_RPATH "${message_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON
)
SKIP_BUILD_RPATH OFF:告诉CMake生成适当的RPATH,以便能够在构建树中运行可执行文件。
UILD_WITH_INSTALL_RPATH OFF:关闭生成可执行目标,使其RPATH调整为与安装树的RPATH相同。在构建树中不运行可执行文件。
INSTALL_RPATH “${message_RPATH}”:将已安装的可执行目标的RPATH设置为先前的路径。
INSTALL_RPATH_USE_LINK_PATH ON:告诉CMake将链接器搜索路径附加到可执行文件的RPATH中。
find_package(LibaryName)根据对应CMAKE_MODULE_PATH找到对应的Find<LibaryName>.cmake,一般来说,有如下三下变量(你也可以定义成别的名字)。
<LibaryName>_FOUND 是否找到库
<LibaryName>_INCLUDE_DIR <LibaryName>_INCLUDES 库头文件目录
<LibaryName>_LIBRARY <LibaryName>_LIBRARIES 库链接文件路径
如果我们引用的第三方库没有提供Find<LibaryName>.cmake,我们可以自己写,只需要填充上面上面变量,就可以使用find_package,实际一般用如下几个函数确定这三个变量,而这几个函数默认都会去CMAKE_PREFIX_PATH查找:
FIND_PACKAGE_HANDLE_STANDARD_ARGS:<LibaryName>_FOUND
find_path:获得<LibaryName>_INCLUDE_DIR目录。
find_library:获得<LibaryName>_LIBRARY 目录。
结合前面的include_directories/link_libraries引用对应的<LibaryName>_INCLUDE_DIR/<LibaryName>_INCLUDE_DIR就引入第三方库了。
静态库和动态库最本质的区别就是:该库是否被编译进目标(程序)内部。
分别介绍:
静态(函数)库
一般扩展名为(.a或.lib),这类的函数库通常扩展名为libxxx.a或xxx.lib 。
这类库在编译的时候会直接整合到目标程序中,所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库更新,需要重新编译。
动态函数库
动态函数库的扩展名一般为(.so或.dll),这类函数库通常名为libxxx.so或xxx.dll 。
与静态函数库被整个捕捉到程序中不同,动态函数库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
https://blog.csdn.net/bit_clearoff/article/details/53965514
Windows中我们常用vs来编译编写好的C和C++代码;vs把编辑器,编译器和调试器等工具都集成在这一款工具
指定.cmake文件的路径
CMAKE_TOOLCHAIN_FILE
https://blog.csdn.net/chouhuan1877/article/details/100808689?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
以下代码表示如果USE_LIBRARY
为ON
,MAKE_STATIC_LIBRARY
默认值为OFF
,否则MAKE_SHARED_LIBRARY
默认值为ON
。
option(USE_LIBRARY "Compile sources into a library" OFF)
include(CMakeDependentOption)
CMakeDependentOption
cmake_dependent_option(
MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF
"USE_LIBRARY" ON
)
cmake_dependent_option(
MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON
"USE_LIBRARY" ON
)
VERBOSE=1,详细输出cmake生成的makefile调用的确切g++命令
-O3
-O2
CXX_STANDARD 14
CXX_EXTENSIONS OFF
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE 1
Release —— 在编译器中使用命令: -O3 -DNDEBUG 可选择此版本。
Debug ——在编译器中使用命令: -g 可选择此版本。
MinSizeRel—— 最小体积版本。在编译器中使用命令:-Os -DNDEBUG可选择此版本。
RelWithDebInfo—— 既优化又能调试。
原文链接:https://blog.csdn.net/weixin_42089190/article/details/106420270
-fPIC 添加fPIC选项生成的动态库,是位置无关。这样的代码本身就能被放到线性地址空间的任意位置,无需修改就能运行。通常的方法是获取指令指针的值,加上一个偏移得到全局变量/函数的地址。添加 -fPIC选项的源文件对于它引用的函数头文件编写有较宽松的尺度。比如只需要包含声明的函数的头文件,即使没有相应的 C 文件来实现,编译成 .so 库照样可以通过。
fPIC 解释
position-independent code (PIC):用于生成位置无关代码。位置无关代码,可以理解为代码无绝对跳转,跳转都为相对跳转。生成动态库时,需要加上-fPIC选项。
-Wall
两种变量的理解Norma Variables Cache Variables
CMake语法—缓存变量(Cache Variable)
aux_source_directory用法
aux_source_directory( )
设置外部cmake项目的编译输出位置
set_property(DIRECTORY PROPERTY EP_BASE ${CMAKE_BINARY_DIR}/subprojects)
设置编译好FFTW3.dll和头文件的安装输出位置
set(STAGED_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/stage)
cmake-cookbo-8.1
ExternalProject_Add(${PROJECT_NAME}_core
SOURCE_DIR
${CMAKE_CURRENT_LIST_DIR}/src
CMAKE_ARGS
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
-DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS}
-DCMAKE_CXX_STANDARD_REQUIRED=${CMAKE_CXX_STANDARD_REQUIRED}
CMAKE_CACHE_ARGS
-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS}
BUILD_ALWAYS
1
INSTALL_COMMAND
""
)
可以支持在线下载第三方库FFTW3且编译输出到指定目录下——cookbook8.3、8.4
if(FFTW3_FOUND)
get_property(_loc TARGET FFTW3::fftw3 PROPERTY LOCATION)
message(STATUS "Found FFTW3: ${_loc} (found version ${FFTW3_VERSION})")
add_library(fftw3_external INTERFACE) # dummy
else()
message(STATUS "Suitable FFTW3 could not be located. Downloading and building!")
include(ExternalProject)
ExternalProject_Add(fftw3_external
URL
http://www.fftw.org/fftw-3.3.8.tar.gz
URL_HASH
MD5=8aac833c943d8e90d51b697b27d4384d
DOWNLOAD_NO_PROGRESS
1
UPDATE_COMMAND
""
LOG_CONFIGURE
1
LOG_BUILD
1
LOG_INSTALL
1
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${STAGED_INSTALL_PREFIX}
-DBUILD_TESTS=OFF
CMAKE_CACHE_ARGS
-DCMAKE_C_FLAGS:STRING=$<$<BOOL:WIN32>:-DWITH_OUR_MALLOC>
)
include(GNUInstallDirs)
set(
FFTW3_DIR ${STAGED_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/cmake/fftw3
CACHE PATH "Path to internally built FFTW3Config.cmake"
FORCE
)
endif()
````FetchContent`模块,它将提供需要的声明、查询和填充依赖项函数,也可以支持在线下载第三方库且编译输出到指定目录下
```bash
include(FetchContent)#首先包括`FetchContent`模块
FetchContent_Declare(#声明内容——名称、存储库位置和要获取的精确版
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.8.0
)
FetchContent_GetProperties(googletest)#查询内容是否已经被获取/填充
add_subdirectory(
${googletest_SOURCE_DIR}
${googletest_BINARY_DIR}
)
https://cmake.org/cmake/help/v3.14/manual/cmake.1.html?highlight=open#command-line-tool-mode
cmake -G的G应该是Generator的意思,该语句可以设置IDE,比如Xcode
cmake -D 是用来设置编译器的
-D 用于传入配置项option,更新或创建缓存入口
cmake -D PYTHON_EXECUTABLE=/custom/location/python .. #指定python解释器的位置
有时需要将-D PYTHON_EXECUTABLE
、-D PYTHON_LIBRARY
和-D PYTHON_INCLUDE_DIR
传递给CMake CLI
-E cmake -E [ …] 命令行工具模式
-Wdev 禁止开发者告警,也会忽略过时信息告警
-P 执行cmake脚本
指定编译类型
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ^
cmake -D CMAKE_CXX_COMPILER=clang++ …
windows 指定编译器架构
cmake -G “Visual Studio 16 2019” -A x64 ^
#判断编译器类型,如果是gcc编译器,则在编译选项中加入c++11支持
if(CMAKE_COMPILER_IS_GNUCXX)
add_compile_options(-std=c++11)
message(STATUS "optional:-std=c++11")
endif(CMAKE_COMPILER_IS_GNUCXX)
下面是对Visual Studio的CMake调用,将为Release和Debug配置生成一个构建树。然后,您可以使--config
标志来决定构建这两个中的哪一个:
```shell
$ mkdir -p build
$ cd build
$ cmake .. -G"Visual Studio 12 2017 Win64" -D CMAKE_CONFIGURATION_TYPES="Release;Debug"
$ cmake --build . --config Release
target_compile_definitions(hello-world PUBLIC “COMPILER_NAME=”${CMAKE_CXX_COMPILER_ID}“”)
将编译类型储存为CACHE变量,使用 FORCE 选项覆盖现有条目。
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
创建库并链接库的方式一:
# generate an object library from sources
add_library(message-objs # 添加message对象
OBJECT
Message.hpp
Message.cpp
)
# this is only needed for older compilers
# but doesn't hurt either to have it
set_target_properties(message-objs
PROPERTIES
POSITION_INDEPENDENT_CODE 1
)
add_library(message-shared
SHARED
$<TARGET_OBJECTS:message-objs>
)
set_target_properties(message-shared
PROPERTIES
OUTPUT_NAME "message"
)
add_library(message-static
STATIC
$<TARGET_OBJECTS:message-objs>
)
set_target_properties(message-static
PROPERTIES
OUTPUT_NAME "message"
)
target_link_libraries(helloworld message-static) # 链接静态库
STATIC库是目标文件的归档文件,在链接其它目标的时候使用。SHARED库会被动态链接(动态链接库),在运行时会被加载。MODULE库是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。默认状态下,库文件将会在于源文件目录树的构建目录树的位置被创建,该命令也会在这里被调用。
MODULE
选项将生成一个插件库,也就是动态共享对象(DSO),没有动态链接到任何可执行文件,但是仍然可以在运行时加载。由于我们使用C++来扩展Python,所以Python解释器需要能够在运行时加载我们的库。使用MODULE
选项进行add_library
,可以避免系统在库名前添加前缀(例如:Unix系统上的lib)。
cmake : add_library详解STATIC(静态库)/SHARED(动态库)/MODULE(模块库)
创建库并链接库的方式二:
list(APPEND _sources Message.hpp Message.cpp)
add_library(message STATIC ${_sources})
target_link_libraries(hello-world message)
cmake官方解释
通过在C++类中减少对显式DLL导出标记的需求,这简化了移植项目到Windows。
官方文档: https://cmake.org/cmake/help/v3.12/manual/cmake-buildsystem.7.html#object-libraries
CMake: Public VS Private VS Interface解释与作用
另一种解释:CMake 中的 PUBLIC,PRIVATE,INTERFACE
cmake target_link_libraries() 中<PUBLIC|PRIVATE|INTERFACE> 的区别
#如果目标的头文件.h中包含了依赖的头文件(源文件间接包含),那么这里就是PUBLIC
#如果目标仅源文件中包含了依赖的头文件,那么这里就是PRIVATE
#如果目标的头文件包含依赖,但源文件未包含,那么这里就是INTERFACE
此外还有
target_include_directories():指定目标包含的头文件路径。
target_link_libraries():指定目标链接的库。
target_compile_options():指定目标的编译选项。
一个例子:cmake:target_** 中的 PUBLIC,PRIVATE,INTERFACE
包含CMakeLists.txt子目录:
一、
# defines targets and sources
add_subdirectory(src)
# defines targets and sources
二、
include(src/CMakeLists.txt)
include(external/CMakeLists.txt)
https://cmake.org/cmake/help/latest/manual/cmake-compile-features.7.html
编译功能要求可以通过target_Compile_features()命令指定。例如,如果必须使用cxx_constexpr功能的编译器支持编译目标:
add_library(mylib requires_constexpr.cpp)
target_compile_features(mylib PRIVATE cxx_constexpr)
例如,如果在项目的头文件中广泛使用C++ 11特性,那么客户端必须使用不小于C++ 11的编译器模式。可通过以下代码请求:
target_compile_features(mylib PUBLIC cxx_std_11)
编译器:
AppleClang: Apple Clang for Xcode versions 4.4+.
Clang: Clang compiler versions 2.9+.
GNU: GNU compiler versions 4.4+.
MSVC: Microsoft Visual Studio versions 2010+.
SunPro: Oracle SolarisStudio versions 12.4+.
Intel: Intel compiler versions 12.1+.
try_compile(
omp_taskloop_test_1
${_scratch_dir}
SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/taskloop.cpp
LINK_LIBRARIES
OpenMP::OpenMP_CXX
)
message(STATUS "Result of try_compile: ${omp_taskloop_test_1}")
include(CheckCXXSourceCompiles)
file(READ ${CMAKE_CURRENT_SOURCE_DIR}/taskloop.cpp _snippet)
set(CMAKE_REQUIRED_LIBRARIES OpenMP::OpenMP_CXX)
check_cxx_source_compiles("${_snippet}" omp_taskloop_test_2)
unset(CMAKE_REQUIRED_LIBRARIES)
检查编译器标志
list(APPEND CXX_BASIC_FLAGS "-g3" "-O1")
include(CheckCXXCompilerFlag)
set(ASAN_FLAGS "-fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_REQUIRED_FLAGS ${ASAN_FLAGS})
check_cxx_compiler_flag(${ASAN_FLAGS} asan_works)
unset(CMAKE_REQUIRED_FLAGS)
if(asan_works)
string(REPLACE " " ";" _asan_flags ${ASAN_FLAGS})
add_executable(asan-example asan-example.cpp)
target_compile_options(asan-example
PUBLIC
${CXX_BASIC_FLAGS}
${_asan_flags}
)
target_link_libraries(asan-example
PUBLIC
${_asan_flags}
)
endif()
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-02 LANGUAGES CXX)
add_executable(hello-world hello-world.cpp)
# let the preprocessor know about the system name
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_compile_definitions(hello-world PUBLIC "IS_LINUX")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_compile_definitions(hello-world PUBLIC "IS_WINDOWS")
endif()
#pragma once是一个比较常用的C/C++预处理指令,只要在头文件的最开始加入这条预处理指令,就能够保证头文件只被编译一次。
C/C++文件中的预处理指令#define,#ifdef,#ifndef,#endif…可以知道当前是在哪个操作系统
#include
#include
#include
std::string say_hello() {
#ifdef IS_WINDOWS
return std::string("Hello from Windows!");
#elif IS_LINUX
return std::string("Hello from Linux!");
#else
return std::string("Hello from an unknown system!");
#endif
}
int main() {
std::cout << say_hello() << std::endl;
return EXIT_SUCCESS;
}
Query host system specific information.
查询主机系统特定信息
cmake_host_system_information(RESULT QUERY ...)
cmake官方
find_package
命令找到Python解释器使用find_package
(EXACT)关键字,限制CMake检测特定的版本
include(CMakePrintHelpers)
cmake_print_variables(_status _hello_world)
以下代码在cmake中执行python代码
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')" #-c应该表示接下来输入字符串
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
#根据您执行的命令,它可能会为变量设置尾随空格。
#如果这种行为不可取,可以使用选项OUTPUT_STRIP_Training_WHITESPACE作为“execute_process”函数的最后一个参数
)
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import ${_module_name}; print(${_module_name}.__version__)"
OUTPUT_VARIABLE _stdout
ERROR_VARIABLE _stderr
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
Message("OUTPUT_VARIABLE=${OUTPUT_VARIABLE}")
if(_stderr MATCHES "ModuleNotFoundError")
message(STATUS "Module ${_module_name} not found")
else()
message(STATUS "Found module ${_module_name} v${_stdout}")
endif()
FindPackageHandleStandardArgs模块提供了用于实现Find_package()调用的Find模块中的函数。
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(NumPy
FOUND_VAR NumPy_FOUND
REQUIRED_VARS NumPy
VERSION_VAR _numpy_version
)
查找第三方库的指定模块
# 查找Boost库的filesystem模块
find_package(Boost 1.54 REQUIRED COMPONENTS filesystem)
# target_link_libraries(hello-world
PUBLIC
Boost::filesystem
)
#如果Boost库安装在非标准位置,可以在配置时使用`BOOST_ROOT`变量传递Boost安装的根目录
cmake -D BOOST_INCLUDEDIR=/custom/boost/include -DBOOST_LIBRARYDIR=/custom/boost/lib
有四种方式可用于找到依赖包:
Config.cmake
,ConfigVersion.cmake
和Targets.cmake
,通常会在包的标准安装位置查找。find-module
。pkg-config
,如本节的示例所示。find
模块,Cmake-CookBook3章10节有一个自定义的find模块。cmake使用指定opencv版本
find_package()函数使用前设置_DIR。我们可以在调用cmake时将这个目录传给cmake。由于其优先级最高,因此cmake会优先从该目录中寻找,这样我们就可以随心所欲的配置cmake使其找到我们希望它要找到的包。
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_MODULE_PATH "/my/custom/module/path;${CMAKE_MODULE_PATH}")
Ubuntu 16.04 + OpenCV 自定义环境变量 pkg-config / PKG_CONFIG_PATH
find_package Cmake官方解释
stackoverflow对find_package默认搜索路径的解释
pkg-config相关命令详解说明
Windows中使用pkg-config程序
Cmake-CookBook 4章2节catch测试模块
option(ENABLE_UNIT_TESTS “Enable unit tests” ON)
$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build .
$ ctest #执行cmake中的测试命令
ctest -C debug #设置ctest的configure模式
ctest -T memcheck#检测内存泄漏
option(ENABLE_UNIT_TESTS "Enable unit tests" ON)
if(ENABLE_UNIT_TESTS) #启动ENABLE_UNIT_TESTS模块
include(fetch_git_repo.cmake)
fetch_git_repo(
googletest
${CMAKE_BINARY_DIR}/_deps
https://github.com/google/googletest.git
release-1.8.0
)
# when building with Visual Studio
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Prevent GoogleTest from using PThreads
set(gtest_disable_pthreads ON CACHE BOOL "" FORCE)
add_subdirectory(
${googletest_SOURCE_DIR}
${googletest_BINARY_DIR}
)
set_tests_properties(example PROPERTIES WILL_FAIL true)#将属性`WILL_FAIL`设置为`true`
set_tests_properties(example PROPERTIES TIMEOUT 10)#为测试指定时限,设置为10秒,超过了这个设置时间则失败
set_tests_properties(j PROPERTIES COST 4.5) #
ctest --parallel 4#在4个内核上运行测试,还可以使用环境变量CTEST_PARALLEL_LEVEL
将其设置为所需的级别
ctest core 1: aeeeiiiiiii core 2: bfffjjjjjjjjj core 3: cggg core 4: dhhhhh#设置测量顺序
set_tests_properties(j PROPERTIES COST 4.5)
测试属性label
set_tests_properties(
feature-a
feature-b
feature-c
PROPERTIES
LABELS "quick"
)
# 结果:
long = 0.62 sec*proc (3 tests)
quick = 0.28 sec*proc (3 tests)
ctest -R feature #运行名称中包含feature的程序测试时间
ctest -L long #运行所有长label的测试时间
ctest --help#查看使用方法
定义了一个文本固件,并将其称为my-fixture
。我们为安装测试提供了FIXTURES_SETUP
属性,并为清理测试了FIXTURES_CLEANUP
属性,并且使用FIXTURES_REQUIRED
,我们确保测试feature-a
和feature-b
都需要安装和清理步骤才能运行。将它们绑定在一起,可以确保在定义良好的状态下,进入和离开相应的步骤。
set_tests_properties(
setup
PROPERTIES
FIXTURES_SETUP my-fixture
)
set_tests_properties(
feature-a
feature-b
PROPERTIES
FIXTURES_REQUIRED my-fixture
)
set_tests_properties(
cleanup
PROPERTIES
FIXTURES_CLEANUP my-fixture
)
# Result:
Start 1: setup
1/4 Test #1: setup ............................ Passed 0.09 sec
Start 2: feature-a
2/4 Test #2: feature-a ........................ Passed 0.04 sec
Start 3: feature-b
3/4 Test #3: feature-b ........................ Passed 0.04 sec
Start 4: cleanup
4/4 Test #4: cleanup .......................... Passed 0.04 sec
add_executable(cpp_test test.cpp)
target_link_libraries(cpp_test sum_integers)
macro(add_catch_test _name _cost)
math(EXPR num_macro_calls "${num_macro_calls} + 1")
message(STATUS "add_catch_test called with ${ARGC} arguments: ${ARGV}")
set(_argn "${ARGN}")
if(_argn)
message(STATUS "oops - macro received argument(s) we did not expect: ${ARGN}")
endif()
add_test(
NAME
${_name}
COMMAND
$<TARGET_FILE:cpp_test> #执行cpp_test,及参数设置
[${_name}] --success --out #cpp_test --success --out 1.log --durations yes 可直接在cmd中运行
${PROJECT_BINARY_DIR}/tests/${_name}.log --durations yes
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
)
set_tests_properties(
${_name}
PROPERTIES
COST ${_cost} #通过将此属性设置为浮点值来明确定义测试的成本
)
endmacro()
set(num_macro_calls 0)
add_catch_test(short 1.5)
add_catch_test(long 2.5 extra_argument)
message(STATUS "in total there were ${num_macro_calls} calls to add_catch_test")
CMAKE_INSTALL_PREFIX表示要安装的路径
当只要求安装库,我们可以执行以下步骤:
cmake -D COMPONENT=lib -P cmake_install.cmake
构建树、安装树
官方解释:
ARCHIVE
Static libraries are treated as ARCHIVE targets, except those marked with the FRAMEWORK property on OS X (see FRAMEWORK below.) For DLL platforms (all Windows-based systems including Cygwin), the DLL import library is treated as an ARCHIVE target.
LIBRARY
Module libraries are always treated as LIBRARY targets. For non- DLL platforms shared libraries are treated as LIBRARY targets, except those marked with the FRAMEWORK property on OS X (see FRAMEWORK below.)
RUNTIME
Executables are treated as RUNTIME objects, except those marked with the MACOSX_BUNDLE property on OS X (see BUNDLE below.) For DLL platforms (all Windows-based systems including Cygwin), the DLL part of a shared library is treated as a RUNTIME target.
PUBLIC_HEADER
Any PUBLIC_HEADER files associated with a library are installed in the destination specified by the PUBLIC_HEADER argument on non-Apple platforms. Rules defined by this argument are ignored for FRAMEWORK libraries on Apple platforms because the associated files are installed into the appropriate locations inside the framework folder.
install(
TARGETS
message-shared
hello-world_wDSO
ARCHIVE
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
RUNTIME
DESTINATION ${INSTALL_BINDIR}
COMPONENT bin
LIBRARY
DESTINATION ${INSTALL_LIBDIR}
COMPONENT lib
PUBLIC_HEADER
DESTINATION ${INSTALL_INCLUDEDIR}/message
COMPONENT dev
)
将message_RPATH设置为{CMAKE_INSTALL_PREFIX}/{INSTALL_BINDIR} , 这里我在windwos下构建,$CMAKE_INSTALL_PREFIX表示路径是C:\Program Files (x86)。这里message_RPATH= $ORIGIN\ . .\lib,帮助可执行文件寻找链接项。参考https://docs.oracle.com/cd/E19957-01/806-0641/appendixc-16/index.html
file(RELATIVE_PATH _rel ${CMAKE_INSTALL_PREFIX}/${INSTALL_BINDIR} ${CMAKE_INSTALL_PREFIX})
if(APPLE)
set(_rpath "@loader_path/${_rel}")
else()
set(_rpath "\$ORIGIN/${_rel}")
message(STATUS "${_rpath}")
endif()
file(TO_NATIVE_PATH "${_rpath}/${INSTALL_LIBDIR}" message_RPATH)
设置可执行文件相关输出。
SKIP_BUILD_RPATH是一个布尔值,指定是否跳过允许目标从生成树运行的RPATH的自动生成。如果在创建目标时设置了变量CMAKE_SKIP_BUILD_RPATH,则该属性由该变量的值初始化。
BUILD_WITH_INSTALL_RPATH是一个布尔值,指定是否将生成树中的目标链接到INSTALL_RPATH。这优先于SKIP_BUILD_RPATH,避免了安装前重新链接的需要。
INSTALL_RPATH_USE_LINK_PATH是一个布尔值,如果设置为true,则会将链接器搜索路径中以及项目外部的目录附加到INSTALL_RPATH。如果在创建目标时设置了变量CMAKE_INSTALL_RPATH_USE_LINK_PATH,则该属性由变量CMAKE_INSTALL_RPATH_USE_LINK_PATH的值初始化。
set_target_properties(hello-world_wDSO
PROPERTIES
MACOSX_RPATH ON
SKIP_BUILD_RPATH OFF # 编译时加上RPATH,通过RPATH解决cmake动态编译后找不到动态链接库问题
BUILD_WITH_INSTALL_RPATH OFF #编译时RPATH不使用安装的RPATH
INSTALL_RPATH "${message_RPATH}"
INSTALL_RPATH_USE_LINK_PATH ON#安装的执行文件不加上RPATH
)
符号可见性:
CXX_VISIBILITY_PRESET hidden
:这将隐藏所有符号,除非显式地标记了其他符号。当使用GNU编译器时,这将为目标添加fvisibility=hidden
标志。VISIBILITY_INLINES_HIDDEN 1
:这将隐藏内联函数的符号。如果使用GNU编译器,这对应于-fvisibility-inlines-hidden
Windows上,这都是默认行为。WINDOWS_EXPORT_ALL_SYMBOLS
属性为ON
来覆盖它。
set_target_properties(message-shared
PROPERTIES
POSITION_INDEPENDENT_CODE 1
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN 1
SOVERSION ${PROJECT_VERSION_MAJOR}
OUTPUT_NAME "message"
DEBUG_POSTFIX "_d"
PUBLIC_HEADER "Message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
MACOSX_RPATH ON
)
CMake编写install export 生成寻找链接项的.cmake文件
Sanitizers 静态和动态代码分析的非常有用的工具。通过使用适当的标志重新编译代码并链接到必要的库,可以检查内存错误(地址清理器)、未初始化的读取(内存清理器)、线程安全(线程清理器)和未定义的行为(未定义的行为清理器)相关的问题。与同类型分析工具相比,Sanitizers带来的性能损失通常要小得多,而且往往提供关于检测到的问题的更详细的信息。缺点是,代码(可能还有工具链的一部分)需要使用附加的标志重新编译
https://github.com/arsenm/sanitizers-cmake
valgrind linux的内存泄漏检查工具。Memcheck这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误使用情况,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。