0 感悟
1、具体就是讲各种cmake的函数
2、感觉此书从 给出示例-》讲解含义-》稍微拓展 这三个层面去讲。讲解不是太清晰,可能还是自己太多知识盲区。
3、问题:
1)find_package和include区别?
2)cmakeList和cmake后缀区别?
1 从可执行文件到库
1.1 单个源文件编译成可执行文件
1、书写一个hello-world.cpp,在里面写一个CmakeLists.txt。
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES CXX)
add_executable(hello-world hello-world.cpp)
2、创建build目录,在build中配置项目:
建立目录是为了不把生成文件生入其中。
cmake ..是为了配置项目。
$ mkdir -p build
$ cd build
$ cmake ..
-- The CXX compiler identification is GNU 8.1.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/cmake-cookbook/chapter-01/recipe-01/cxx-example/build
3、在build目录中编译可执行文件:
cmake --build .是为了构建项目。
$ cmake --build .
Scanning dependencies of target hello-world
[ 50%] Building CXX object CMakeFiles/hello-world.dir/hello-world.cpp.o
[100%] Linking CXX executable hello-world
[100%] Built target hello-world
1.2 切换生成器
1、查看生成器
cmake --help
2、指定生成器
$ mkdir -p build
$ cd build
$ cmake -G Ninja ..
-- The CXX compiler identification is GNU 8.1.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/user/cmake-cookbook/chapter-01/recipe-02/cxx-exampl
$ cmake --build .
[2/2] Linking CXX executable hello-world
1.3 构建和链接静态库和动态库
普通动态或静态库
存在Message.hpp和Message.cpp编译成一个库,hello-world.cpp编译成可执行程序并链接到此库,过程如下:
1、创建目标——静态库。
- 第二个参数为STATIC或SHARED或OBJECT或MODULE
- 生成的库的实际名称将由CMake通过在前面添加前缀
lib
和适当的扩展名作为后缀来形成。
add_library(message STATIC Message.hpp Message.cpp )
2、创建hello-world
可执行文件
add_executable(hello-world hello-world.cpp)
3、将目标库链接到可执行程序
target_link_libraries(hello-world message)
4、CmakeList关键点如上,按要求编译即可
OBJECT库
感觉首先是add_library(target_xxx OBJECT file_list)生成对象库,然后后面通过$<TARGET_OBJECTS:message-objs>(生成器表达式)引用之
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-03 LANGUAGES CXX)
add_library(message-objs
OBJECT # 此处add_library为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 # 此处add_library为SHARED
$<TARGET_OBJECTS:message-objs>
)
add_library(message-static
STATIC # 此处add_library为STATIC
$<TARGET_OBJECTS:message-objs>
)
add_executable(hello-world hello-world.cpp)
target_link_libraries(hello-world message-static)
1.4 用条件句控制编译
前面的讲解都是一组源文件到单个可执行文件,但也可以条件编译,根据不同情况编译不同目标:
USE_LIBRARY为引入的一个控制变量,BUILD_SHARED_LIBS 为本来存在的一个全局变量
# introduce a toggle for using a library
set(USE_LIBRARY OFF)
message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
# BUILD_SHARED_LIBS is a global flag offered by CMake
# to toggle the behavior of add_library
set(BUILD_SHARED_LIBS OFF) # 用处是可以省略掉add_library的第二个参数
# list sources
list(APPEND _sources Message.hpp Message.cpp)
if(USE_LIBRARY)
# add_library will create a static library
# since BUILD_SHARED_LIBS is OFF
add_library(message ${_sources})
add_executable(hello-world hello-world.cpp)
target_link_libraries(hello-world message)
else()
add_executable(hello-world hello-world.cpp ${_sources})
endif()
1.5 向用户显示选项
上面的USE_LIBRARY 为写死的,用户不能实时修改,可以使用
cmake -D USE_LIBRARY=ON ..
cmake内部需要这样写:默认值是OFF,支持外部修改
option(USE_LIBRARY "Compile sources into a library" OFF)
1.6 指定编译器
1、指定编译器
$ cmake -D CMAKE_CXX_COMPILER=clang++ ..
2、查看全点的编译器标志
$ cmake --system-information information.txt
3、通过打印方式查看已设的标志
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-06 LANGUAGES C CXX)
message(STATUS "Is the C++ compiler loaded? ${CMAKE_CXX_COMPILER_LOADED}")
if(CMAKE_CXX_COMPILER_LOADED)
message(STATUS "The C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
message(STATUS "Is the C++ from GNU? ${CMAKE_COMPILER_IS_GNUCXX}")
message(STATUS "The C++ compiler version is: ${CMAKE_CXX_COMPILER_VERSION}")
endif()
message(STATUS "Is the C compiler loaded? ${CMAKE_C_COMPILER_LOADED}")
if(CMAKE_C_COMPILER_LOADED)
message(STATUS "The C compiler ID is: ${CMAKE_C_COMPILER_ID}")
message(STATUS "Is the C from GNU? ${CMAKE_COMPILER_IS_GNUCC}")
message(STATUS "The C compiler version is: ${CMAKE_C_COMPILER_VERSION}")
endif()
1.7 切换构建类型
cmake -D CMAKE_BUILD_TYPE=Debug ..
# 或者
cmake -D CMAKE_BUILD_TYPE=Release ..
1.8 设置编译器选项(compile options)
0、选项是指-fPIC这种
list(APPEND flags "-fPIC" "-Wall")
if(NOT WIN32)
list(APPEND flags "-Wextra" "-Wpedantic")
endif()
... # 主要为geometry目标的列表
target_compile_options(geometry
PRIVATE
${flags} # 可以直接展开写或采用变量
)
1、编译选项的可见性
PRIVATE:只用于给定的目标,不会传递跟目标相关的目标
INFTERFACE:会传递给跟目标相关的目标(一层?)
PUBLIC:应用于给定目标和使用它的目标(多层?
)
CMake的链接选项:PRIVATE,INTERFACE,PUBLIC - 知乎
1.9 为语言设定标准
set_target_properties(animal-farm
PROPERTIES
CXX_STANDARD 14
CXX_EXTENSIONS OFF # 只用iso 标准,不使用扩展
CXX_STANDARD_REQUIRED ON # 如果这个版本不可用,依次向更低版本查找
)
1.10 使用控制流
list(
APPEND sources_with_lower_optimization
geometry_circle.cpp
geometry_rhombus.cpp
)
message(STATUS "Setting source properties using IN LISTS syntax:")
foreach(_source IN LISTS sources_with_lower_optimization)
set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS -O2)
message(STATUS "Appending -O2 flag for ${_source}")
endforeach()
2 检测环境
2.1 检测操作系统
1、判断当前属于何种操作系统,""填写uname -s中的:
$uname -s
$Linux
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Configuring on/for Linux")
2.2 处理与平台相关的源代码(compile definitions)
1.cmake 编译选项写法
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_compile_definitions(hello-world PUBLIC "IS_LINUX")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_compile_definitions(hello-world PUBLIC "IS_MACOS")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_compile_definitions(hello-world PUBLIC "IS_WINDOWS")
endif()
2、 代码使用
#include <cstdlib>
#include <iostream>
#include <string>
std::string say_hello() {
#ifdef IS_WINDOWS
return std::string("Hello from Windows!");
#elif IS_LINUX
return std::string("Hello from Linux!");
#elif IS_MACOS
return std::string("Hello from macOS!");
#else
return std::string("Hello from an unknown system!");
#endif
}
int main() {
std::cout << say_hello() << std::endl;
return EXIT_SUCCESS;
}
2.3 处理与编译器相关的源代码
cmake配置时进行预处理定义,并传递给预处理器
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-03 LANGUAGES CXX)
# define executable and its source file
add_executable(hello-world hello-world.cpp)
target_compile_definitions(hello-world PUBLIC "COMPILER_NAME=\"${CMAKE_CXX_COMPILER_ID}\"")
# let the preprocessor know about the compiler vendor
if(CMAKE_CXX_COMPILER_ID MATCHES Intel)
target_compile_definitions(hello-world PUBLIC "IS_INTEL_CXX_COMPILER")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
target_compile_definitions(hello-world PUBLIC "IS_GNU_CXX_COMPILER")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES PGI)
target_compile_definitions(hello-world PUBLIC "IS_PGI_CXX_COMPILER")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES XL)
target_compile_definitions(hello-world PUBLIC "IS_XL_CXX_COMPILER")
endif()
# etc ...
简化代码:
target_compile_definitions(hello-world
PUBLIC "IS_${CMAKE_CXX_COMPILER_ID}_CXX_COMPILER"
)
2.4 检测处理器体系结构
1、cmake配置
... # arch-dependent是编译目标
# 定义32位还是64位
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
target_compile_definitions(arch-dependent PUBLIC "IS_64_BIT_ARCH")
message(STATUS "Target is 64 bits")
else()
target_compile_definitions(arch-dependent PUBLIC "IS_32_BIT_ARCH")
message(STATUS "Target is 32 bits")
endif()
# 定义处理器架构
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i386")
message(STATUS "i386 architecture detected")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i686")
message(STATUS "i686 architecture detected")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64")
message(STATUS "x86_64 architecture detected")
else()
message(STATUS "host processor architecture is unknown")
endif()
target_compile_definitions(arch-dependent
PUBLIC "ARCHITECTURE=${CMAKE_HOST_SYSTEM_PROCESSOR}"
)
2、代码使用
#include <cstdlib>
#include <iostream>
#include <string>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
std::string say_hello()
{
std::string arch_info(TOSTRING(ARCHITECTURE)); // 使用ARCHITECTURE获取cpu位数
arch_info += std::string(" architecture. ");
#ifdef IS_32_BIT_ARCH // 隔离体系结构
return arch_info + std::string("Compiled on a 32 bit host processor.");
#elif IS_64_BIT_ARCH
return arch_info + std::string("Compiled on a 64 bit host processor.");
#else
return arch_info + std::string("Neither 32 nor 64 bit, puzzling ...");
#endif
}
int main()
{
std::cout << say_hello() << std::endl;
return EXIT_SUCCESS;
}
2.5 检测处理器指令集
0、需要3.10或更高版本
1、主要把config.h.in 通过命令生成config.h
configure_file(config.h.in config.h @ONLY)
2.6 为Eigen库使能向量化
主要就是根据情况设置_CXX_FLAGS,然后在编译目标文件时决定是否加上此编译选项
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-06 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Eigen3 3.3 REQUIRED CONFIG) # 查找Eigen库的头文件
include(CheckCXXCompilerFlag) # include就像调用函数前包含头文件
check_cxx_compiler_flag("-march=native" _march_native_works)
check_cxx_compiler_flag("-xHost" _xhost_works)
set(_CXX_FLAGS) # 先定义为空,if语句中根据情况决定实际值
if(_march_native_works)
message(STATUS "Using processor's vector instructions (-march=native compiler flag set)")
set(_CXX_FLAGS "-march=native")
elseif(_xhost_works)
message(STATUS "Using processor's vector instructions (-xHost compiler flag set)")
set(_CXX_FLAGS "-xHost")
else()
message(STATUS "No suitable compiler flag found for vectorization")
endif()
add_executable(linear-algebra-unoptimized linear-algebra.cpp)
target_link_libraries(linear-algebra-unoptimized
PRIVATE
Eigen3::Eigen
)
add_executable(linear-algebra linear-algebra.cpp)
target_compile_options(linear-algebra
PRIVATE
${_CXX_FLAGS}
)
target_link_libraries(linear-algebra
PRIVATE
Eigen3::Eigen
)
3 检测外部库和程序 (find命令)
3.1 检测Python解释器
find_package主要用于查找依赖,要找的是FindPythonInterp.cmake(cmake模块文件)
find命令使得模块文件中的cmake命令得到运行。查找到就可以使用模块内设置的Cmake变量。
# 查找包
find_package(PythonInterp REQUIRED)
# 使用其中的命令,比如PYTHON_EXECUTABLE
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')"
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
3.2 检测Python库
1、FindPythonLibs.cmake
模块将查找Python头文件和库的标准位置
#解释器
find_package(PythonInterp REQUIRED)
# python库
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
add_executable(hello-embedded-python hello-embedded-python.c)
target_include_directories(hello-embedded-python
PRIVATE
${PYTHON_INCLUDE_DIRS}
)
target_link_libraries(hello-embedded-python
PRIVATE
${PYTHON_LIBRARIES}
)
2、不在标准安装目录时,通过-D选项来传递PYTHON_LIBRARY
和PYTHON_INCLUDE_DIR
3.3 检测Python模块和包(检测numpy可用)
1、查找解释器、头文件和库
find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
2、查找numpy 的位置和版本
# Find NumPy location
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import re, numpy; print(re.compile('/__init__.py.*').sub('',numpy.__file__))"
RESULT_VARIABLE _numpy_status
OUTPUT_VARIABLE _numpy_location
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT _numpy_status)
set(NumPy ${_numpy_location} CACHE STRING "Location of NumPy")
endif()
# Find NumPy version
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import numpy; print(numpy.__version__)"
OUTPUT_VARIABLE _numpy_version
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
3、查找numpy
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(NumPy
FOUND_VAR NumPy_FOUND
REQUIRED_VARS NumPy
VERSION_VAR _numpy_version
)
4、成功find到包后的输出
-- Found PythonInterp: /usr/bin/python (found version "3.6.5")
-- Found PythonLibs: /usr/lib/libpython3.6m.so (found suitable exact version "3.6.5")
-- Found NumPy: /usr/lib/python3.6/site-packages/numpy (found version "1.14.3")
5、把use_numpy.py复制到build目录。target_sources
命令,它将依赖项添加到${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
;这样做是为了确保构建目标,能够触发前面的add_command命令
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
)
target_sources(pure-embedding
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
)
3.4 检测BLAS和LAPACK数学库
3.5 检测OpenMP的并行环境
0、OpenMP为并行库
1、cmake写法,主要依赖FindOpenMP.cmake模块
find_package(OpenMP REQUIRED)
add_executable(example example.cpp)
target_link_libraries(example
PUBLIC
OpenMP::OpenMP_CXX
)
2、OpenMP::OpenMP_CXX里已包含要设置的编译器标志,比如目录和链接库,通过下面去打印:
include(CMakePrintHelpers)
cmake_print_properties(
TARGETS
OpenMP::OpenMP_CXX
PROPERTIES
INTERFACE_COMPILE_OPTIONS
INTERFACE_INCLUDE_DIRECTORIES
INTERFACE_LINK_LIBRARIES
)
3.6 检测MPI的并行环境
0、MPI为消息传递接口,是OpenMP的补充,分布式上的。
1、3.9及以上cmake配置程序,主要在FindMPI.cmake
find_package(MPI REQUIRED)
add_executable(hello-mpi hello-mpi.cpp)
target_link_libraries(hello-mpi
PUBLIC
MPI::MPI_CXX
)
2、3.9以下得写好多句话
add_executable(hello-mpi hello-mpi.c)
target_compile_options(hello-mpi
PUBLIC
${MPI_CXX_COMPILE_FLAGS}
)
target_include_directories(hello-mpi
PUBLIC
${MPI_CXX_INCLUDE_PATH}
)
target_link_libraries(hello-mpi
PUBLIC
${MPI_CXX_LIBRARIES}
)
3.7 检测Eigen库
0、也是矩阵和向量计算库
1、cmake 人家一般是
find_package(OpenMP REQUIRED)
Eigen3 多了个CONFIG,所以不会使用FindEigen3.cmake
模块,而是通过特定的Eigen3Config.cmake
,Eigen3ConfigVersion.cmake
和Eigen3Targets.cmake
提供Eigen3安装的标准位置(<installation-prefix>/share/eigen3/cmake
)。这种包定位模式称为“Config”模式,比Find<package>.cmake
方式更加通用。
find_package(Eigen3 3.3 REQUIRED CONFIG)
3.8 检测Boost库
3.9 检测外部库:Ⅰ. 使用pkg-config
1、如果Find<LibName>.cmake也不提供,<Libname>Config.cmake也不提供,就要用下面这俩方法:pkg-config 或者 自定义find-package
模块
3.10 检测外部库:Ⅱ. 自定义find模块
1、cmakeList写查找。依赖cmake文件
find_package(ZeroMQ REQUIRED)
2、自己写find-package模块(FindZeroMQ.cmake
文件)
4 创建和运行测试
4.3 Google Test库进行单元测试
5 配置时和构建时的操作
5.1 使用平台无关的文件操作
1、可以调用命令来打包或创建文件等,使用add_custom_target命令来完成
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"
)
5.2 配置时运行自定义命令
1、主要讲解通过execute_process运行一段python代码
find_package(PythonInterp REQUIRED)
# this is set as variable to prepare
# for abstraction using loops or functions
set(_module_name "cffi")
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
)
会从当前正在执行的CMake进程中派生一个或多个子进程,从而执行一套命令。每个命令的输出通过管道进入到下一个命令中。
5.3 构建时运行自定义命令:Ⅰ. 使用add_custom_command
1、wrap_BLAS_LAPACK_sources就是生成的目标
set(wrap_BLAS_LAPACK_sources
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.cpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.cpp
)
add_custom_command(
OUTPUT
${wrap_BLAS_LAPACK_sources} #绑定target
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
)
2、使用解压的文件。用到了target_sources/target_include_directories/target_link_libraries
add_library(math "")
target_sources(math
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.cpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.cpp
PUBLIC
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.hpp
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.hpp
)
target_include_directories(math
INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK
)
target_link_libraries(math
PUBLIC
${LAPACK_LIBRARIES}
)
add_executable(linear-algebra linear-algebra.cpp)
target_link_libraries(linear-algebra
PRIVATE
math
)
5.4 构建时运行自定义命令:Ⅱ. 使用add_custom_target
1、主要区别是不需要有输出,因此总会执行
deps/CMakeLists.txt
add_custom_target(BLAS_LAPACK_wrappers
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${MATH_SRCS}
COMMENT
"Intermediate BLAS_LAPACK_wrappers target"
VERBATIM
)
add_custom_command(
OUTPUT
${MATH_SRCS}
COMMAND
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
COMMENT
"Unpacking C++ wrappers for BLAS/LAPACK"
)
5.5 构建时为特定目标运行自定义命令
讲add_custom_command
1、定义可执行目标
add_executable(example "")
target_sources(example
PRIVATE
example.f90
)
2、绑定命令到目标上,这样这些command就会自动被执行
add_custom_command(
TARGET
example
PRE_LINK
COMMAND
${PYTHON_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/echo-file.py
${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/example.dir/link.txt
COMMENT
"link line:"
VERBATIM
)
输出:
[100%] Linking Fortran executable example
link line:
/usr/bin/f95 -O3 -DNDEBUG -O3 CMakeFiles/example.dir/example.f90.o -o example
static size of executable:
160.003 MB
[100%] Built target example
5.6 探究编译和链接命令
1、探究是指命令就是探究类命令,本节研究的是try_compile
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}")
-- Found OpenMP_CXX: -fopenmp (found version "4.5")
-- Found OpenMP: TRUE (found version "4.5")
-- Result of try_compile: TRUE
2、使用check_cxx_source_compiles也可以探究,需要包含CheckCXXSourceCompiles.cmake
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)
message(STATUS "Result of check_cxx_source_compiles: ${omp_taskloop_test_2}")
-- Performing Test omp_taskloop_test_2
-- Performing Test omp_taskloop_test_2 - Success
-- Result of check_cxx_source_compiles: 1
5.7 探究编译器标志命令
1、主要检测编译器选项是否可用。设置要检测的标志
include(CheckCXXCompilerFlag)
set(ASAN_FLAGS "-fsanitize=address -fno-omit-frame-pointer")
set(CMAKE_REQUIRED_FLAGS ${ASAN_FLAGS})
2、检测标志并unset
check_cxx_compiler_flag(${ASAN_FLAGS} asan_works)
unset(CMAKE_REQUIRED_FLAGS)
5.8 探究可执行命令
1、声明工程是C和C++混用
project(recipe-08 LANGUAGES CXX C)
2、找到UUID库
find_package(PkgConfig REQUIRED QUIET)
pkg_search_module(UUID REQUIRED uuid IMPORTED_TARGET)
if(TARGET PkgConfig::UUID)
message(STATUS "Found libuuid")
endif()
3、声明_test_uuid,来绑定要编译和运行的代码
include(CheckCSourceRuns)
set(_test_uuid
"
#include <uuid/uuid.h>
int main(int argc, char * argv[]) {
uuid_t uuid;
uuid_generate(uuid);
return 0;
}
")
4、 运行代码并检测标志(_runs),最后再真正用这个库
set(CMAKE_REQUIRED_LIBRARIES PkgConfig::UUID)
check_c_source_runs("${_test_uuid}" _runs)
unset(CMAKE_REQUIRED_LIBRARIES)
if(NOT _runs)
message(FATAL_ERROR "Cannot run a simple C executable using libuuid!")
endif()
add_executable(use-uuid use-uuid.cpp)
target_link_libraries(use-uuid
PUBLIC
PkgConfig::UUID
)
5.9 使用生成器表达式微调配置和编译
1、CMake分两个阶段生成项目的构建系统:配置阶段(解析CMakeLists.txt
)和生成阶段(实际生成构建环境)。生成器表达式在第二阶段。
2、引入选项USE_MPI去选择MPI并行化,使用cmake -D USE_MPI=OFF可去关闭。
option(USE_MPI "Use MPI parallelization" ON)
if(USE_MPI)
find_package(MPI REQUIRED)
endif()
3、定义可执行目标,根据找到标志MPI_FOUND有条件的设置库依赖项(MPI::MPI_CXX
)和预处理器定义(HAVE_MPI
)
add_executable(example example.cpp)
target_link_libraries(example
PUBLIC
$<$<BOOL:${MPI_FOUND}>:MPI::MPI_CXX>
)
target_compile_definitions(example
PRIVATE
$<$<BOOL:${MPI_FOUND}>:HAVE_MPI>
)
# 或者
if(MPI_FOUND)
target_link_libraries(example
PUBLIC
MPI::MPI_CXX
)
target_compile_definitions(example
PRIVATE
HAVE_MPI
)
endif()
6 生成源码(主要讲cmake配置或生成阶段去生成源码)
6.1 配置时生成源码
6.3 构建时使用python生成源码
7 构建项目(主要讲如果精简cmake代码)
7.1 使用函数和宏重用代码
1、函数可以避免代码重复,cmake也不例外。本节主要定义一个宏,能够替换add_test
和set_tests_properties
,用于定义每组和设置每个测试的预期开销(第4章,第8节)。
2、主cmake去引用子文件夹里的cmake
add_subdirectory(src)
enable_testing()
add_subdirectory(tests)
3、tests里的cmake加一个宏。添加了add_catch_test
宏。这个宏需要两个参数_name
和_cost
,可以在宏中使用这些参数来调用add_test
和set_tests_properties
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>
[${_name}] --success --out
${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")
4、上面宏功能也可以通过函数解决
function(add_catch_test _name _cost)
...
endfunction()
7.2 将CMake源代码分成模块
1、定义了define_colors
宏,并将其放在cmake/colors.cmake。Cmake文件中通过如下方式调用此宏。include(colors)
命令指示CMake搜索${CMAKE_MODULE_PATH}
,查找名称为colors.cmake
的模块。
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(colors) # 或者 include(cmake/colors.cmake)
define_colors()
7.3 编写函数来测试和设置编译器标志
7.4 用指定参数定义函数或宏
7.5 重新定义函数和宏
7.6 使用废弃函数、宏和变量
7.7 add_subdirectory的限定范围
1、主要讲add_subdirectory使用,最外层Cmake文件
# defines targets and sources
add_subdirectory(src)
# contains an "external" library we will link to
add_subdirectory(external)
# enable testing and define tests
enable_testing()
add_subdirectory(tests)
7.8 使用target_sources避免全局变量
1、原写法
add_executable(automata main.cpp)
add_subdirectory(evolution)
add_subdirectory(initial)
add_subdirectory(io)
add_subdirectory(parser)
target_link_libraries(automata
PRIVATE
conversion
evolution
initial
io
parser
)
2、新写法
add_library(automaton "")
add_library(evolution "")
include(${CMAKE_CURRENT_LIST_DIR}/evolution/CMakeLists.txt)
include(${CMAKE_CURRENT_LIST_DIR}/initial/CMakeLists.txt)
include(${CMAKE_CURRENT_LIST_DIR}/io/CMakeLists.txt)
include(${CMAKE_CURRENT_LIST_DIR}/parser/CMakeLists.txt)
add_executable(automata "")
target_sources(automata
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/main.cpp
)
target_link_libraries(automata
PRIVATE
automaton
conversion
)
7.9 组织Fortran项目
10 编写安装程序
前面都是讲配置、构建、测试,此章开始讲安装项目。
10.1 安装项目
安装文件、库和可执行文件是非常基础的任务。
主cmake
1、用户可以通过CMAKE_INSTALL_PREFIX
变量定义安装目录。CMake会给这个变量设置一个默认值:Windows上的C:\Program Files
和Unix上的/usr/local
。我们将会打印安装目录的信息
message(STATUS "Project will be installed to ${CMAKE_INSTALL_PREFIX}")
2、告诉Cmake在哪里构建可执行、静态和动态库目标
include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
3、添加子目录
add_subdirectory(src)
enable_testing()
add_subdirectory(tests)
src的cmake文件
1、安装库、头文件和可执行文件
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
)
安装目录情况
$HOME/Software/recipe-01/
├── bin
│ └── hello-world_wDSO
├── include
│ └── message
│ └── Message.hpp
└── lib64
├── libmessage.so -> libmessage.so.1
└── libmessage.so.1
10.2 生成输出头文件
本节就是讲这样一个需求:静态库下看到所有符号,动态库又想看到尽可能少的符号。
如果要从相同的源文件构建动态和静态库,则需要一种方法来赋予message_EXPORT
预处理变量意义,这两种情况都会出现在代码中。
1、生成动态库message-shared,可执行程序hello-world_wDSO链接此库
CXX_VISIBILITY_PRESET 是隐藏所有符号
VISIBILITY_INLINES_HIDDEN
属性是隐藏内联函数符号
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
)
2、主要讲生成一个头文件messageExport.h,挺神奇的,生成的代码如下
#ifndef message_EXPORT_H
#define message_EXPORT_H
#ifdef message_STATIC_DEFINE
# define message_EXPORT // 代码中使用了class message_EXPORT Message
# define message_NO_EXPORT
#else
# ifndef message_EXPORT
# ifdef message_shared_EXPORTS
/* We are building this library */
# define message_EXPORT __attribute__((visibility("default"))) // 代码中使用了class message_EXPORT Message
# else
/* We are using this library */
# define message_EXPORT __attribute__((visibility("default"))) // 代码中使用了class message_EXPORT Message
# endif
# endif
# ifndef message_NO_EXPORT
# define message_NO_EXPORT __attribute__((visibility("hidden")))
# endif
#endif
#ifndef message_DEPRECATED
# define message_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef message_DEPRECATED_EXPORT
# define message_DEPRECATED_EXPORT message_EXPORT message_DEPRECATED
#endif
#ifndef message_DEPRECATED_NO_EXPORT
# define message_DEPRECATED_NO_EXPORT message_NO_EXPORT message_DEPRECATED
#endif
#if 1 /* DEFINE_NO_DEPRECATED */
# ifndef message_NO_DEPRECATED
# define message_NO_DEPRECATED
# endif
#endif
#endif
3、生成静态库message-static,可执行程序hello-world_wAR链接此库
set_target_properties(message-static
PROPERTIES
POSITION_INDEPENDENT_CODE 1
ARCHIVE_OUTPUT_NAME "message"
DEBUG_POSTFIX "_sd"
RELEASE_POSTFIX "_s"
PUBLIC_HEADER "Message.hpp;${CMAKE_BINARY_DIR}/${INSTALL_INCLUDEDIR}/messageExport.h"
)
4、安装
# <<< Install and export targets >>>
install(
TARGETS
message-shared
message-static
hello-world_wDSO
hello-world_wAR
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
)
10.3 输出目标
1、与上一节的区别主要在这里有了导出。感觉一般项目是直接写cmake?
# <<< Install and export targets >>>
install(
TARGETS
message-shared
message-static
hello-world_wDSO
hello-world_wAR
EXPORT
messageTargets # 自动生成的导出目标文件称为messageTargets.cmake
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
)
install(
EXPORT
messageTargets # 显式指定安装规则
NAMESPACE
"message::"
DESTINATION
${INSTALL_CMAKEDIR}
COMPONENT
dev
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake # 生成一个包含版本信息的文件
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
${PROJECT_SOURCE_DIR}/cmake/messageConfig.cmake.in # 文件夹本来就存在的
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake # 基于上一行生成了实际的CMake配置文件
INSTALL_DESTINATION ${INSTALL_CMAKEDIR}
)
install(
FILES # 为这两个自动生成的配置文件设置了安装规则
${CMAKE_CURRENT_BINARY_DIR}/messageConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/messageConfigVersion.cmake
DESTINATION
${INSTALL_CMAKEDIR}
)
2、最终的安装树
$HOME/Software/recipe-03/
├── bin
│ ├── hello-world_wAR
│ └── hello-world_wDSO
├── include
│ └── message
│ ├── messageExport.h
│ └── Message.hpp
├── lib64
│ ├── libmessage_s.a
│ ├── libmessage.so -> libmessage.so.1
│ └── libmessage.so.1
└── share
└── cmake
└── recipe-03
├── messageConfig.cmake
├── messageConfigVersion.cmake # 生出的几个cmake
├── messageTargets.cmake
└── messageTargets-release.cmake
3、安装后用户非常容易使用,只用在系统上搜索就行
find_package(message VERSION 1 REQUIRED)
10.4 安装超级构建
11 打包项目
本章主要讲PI,就是打包并安装好,不是上章的直接从代码安装
11.1 生成源代码和二进制包(指打包成deb、rpm)
1、主要讲CPACK使用,建了一个文件叫CMakeCPack.cmake
中添加打包指令。可以编各种平台
if(UNIX)
if(CMAKE_SYSTEM_NAME MATCHES Linux)
list(APPEND CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "robertodr")
set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "uuid-dev")
list(APPEND CPACK_GENERATOR "RPM")
set(CPACK_RPM_PACKAGE_RELEASE "1")
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_REQUIRES "uuid-devel")
endif()
endif()
if(WIN32 OR MINGW)
list(APPEND CPACK_GENERATOR "NSIS")
set(CPACK_NSIS_PACKAGE_NAME "message")
set(CPACK_NSIS_CONTACT "robertdr")
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
endif()
if(APPLE)
list(APPEND CPACK_GENERATOR "Bundle")
set(CPACK_BUNDLE_NAME "message")
configure_file(${PROJECT_SOURCE_DIR}/cmake/Info.plist.in Info.plist @ONLY)
set(CPACK_BUNDLE_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
set(CPACK_BUNDLE_ICON ${PROJECT_SOURCE_DIR}/cmake/coffee.icns)
endif()
message(STATUS "CPack generators: ${CPACK_GENERATOR}")
include(CPack) # 将向构建系统添加一个包和一个package_source目标
2、主cmakeList添加引用此cmake
add_subdirectory(src)
enable_testing()
add_subdirectory(tests)
include(CMakeCPack.cmake) # 重点!!
3、得出几个二进制包
message-1.0.0-Linux.rpm
message-1.0.0-Linux.tar.gz
message-1.0.0-Linux.zip