Cmake菜谱读书笔记

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
  1. if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  2.   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_LIBRARYPYTHON_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.cmakeEigen3ConfigVersion.cmakeEigen3Targets.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_testset_tests_properties,用于定义每组和设置每个测试的预期开销(第4章,第8节)。

2、主cmake去引用子文件夹里的cmake

add_subdirectory(src)
enable_testing()
add_subdirectory(tests)

3、tests里的cmake加一个宏。添加了add_catch_test宏。这个宏需要两个参数_name_cost,可以在宏中使用这些参数来调用add_testset_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值