cmake详细教程(经验版)

一、参考资料

CMake DSL语言

CMake 快速入门

cmake使用教程

CMake简明教程

CMake 入门实战 | HaHack

Cmake入门和MindsporeLite Cmake文件分析 | 摸黑干活 (fazzie-key.cool)

GitHub - wzpan/cmake-demo: 《CMake入门实战》源码

User Interaction Guide — CMake 3.20.6 Documentation

Home · Wiki · CMake / Community · GitLab (kitware.com)

cmake的一些小经验 - 驭风万里无垠 - C++博客 (cppblog.com)

二、相关介绍

1. 重要说明

  • CMake指令是大小写无关的,即不区分大小写,但建议全部使用大写指令

    add_executable(hello main.cpp hello.cpp)
    ADD_EXECUTABLE(hello main.cpp hello.cpp)
    
  • 变量是大小写相关的,使用 ${}方式取值。但在 if语句 中是直接使用变量名。

    set(HELLO hello.cpp)  # 设置一个变量HELLO,值是hello.cpp
    add_executable(hello main.cpp ${HELLO})
    
  • 使用 make VERBOSE=1 来查看make构建的详细过程。

2. 链接选项-L

链接选项 -L 用于链接时指定显示链接的库的搜索路径(优先级高)。

现代链接器在处理动态库时将**链接时路径(Link-time path)和运行时路径(Run-time path)**分开,用户可以通过-L指定链接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性。

3. 运行选项-R

3.1 问题引入

指定运行时路径有几种方法:拷贝 liba.solibb.so 到系统路径下,修改环境变量 LD_LIBRARY_PATH 或增加配置文件ld.so.confg 中搜索路径。这些都可能改变当前的运行环境,可能导致一些意想不到的事情发生,因此不是很好的方法。对系统影响最小的方法是,在编译的时候指定运行时动态加载库的搜索路径。与之先关的概念有:-R/-rpath/-rpath-linkrpath/runpath

3.2 rpath

rpath,通过 -Wl,-rpath,d1:..:dn 指定。指定的rpath被 硬编码(hard coding/hard compile) 到二进制文件中,规定了二进制文件动态加载其他库时的最优先顺序。相比最粗粒度的路径控制LD_LIBRARY_PATH,rpath可以认为是最细粒度的控制。

通过 readelf -d liba.so | grep pathreadelf -d liba.so 可以发现如下一行,表示二进制的动态加载库最优先搜索路径被写入了该二进制文件中:

0x000000000000001d (RUNPATH)            Library runpath: [/home/nrsl/workspace/clion/experience/rpath/lib]

3.3 rpath-link

链接选项-rpath-link用于在链接时指定间接依赖库的目录(-link的意思为路径并不写入二进制文件因此不做运行时搜索路径使用)。

当一个共享库1依赖另一个共享库2。则链接库1间接依赖库2。当链接器在执行静态链接时遇到这样的依赖项时,它将自动尝试定位所需的共享库并将其包括在链接中(如果它没有显式包含)。在这种情况下,-rpath-link 选项指定要搜索的第一组目录。-rpath-link 选项可以通过指定用冒号分隔的名称列表或多次出现来指定目录名称序列。

当应用程序直接引用一个十分复杂的库时,这个十分复杂的库背后又依赖了很多间接引用库,使用者没必要知道和花时间研究都有什么间接引用库,这时使用-rpath-link就合适了,让所有直接和间接依赖库都和预期的目标运行环境一致

因此,rpath-link用于指定间接依赖的动态库的搜索路径,而-L直接依赖的搜索路径。与rpath的不同在于只在链接期间使用而不在运行期间使用,并且覆盖硬编码到二进制中的rpath

-rpath-link 选项应谨慎使用,因为它会覆盖可能已硬编译到共享库中的搜索路径(rpath,runpath)。可能无意中使用与运行时链接器不同的搜索路径

-rpath-link 与运行时无关!与运行时无关!与运行时无关!

3.4 RUNPATH

新版本的ld可能默认设置了链接选项 -enable-new-dtags-rpath添加到RUNPATH。我们可以设置链接选项-disable-new-dtags来将路径添加到RPATH

新特性中RUNPATH会让RPATH失效,且优先级低于RPATHLD_LIBRARY_PATH环境变量。虽然RPATH优先级高,但是当有RUNPATH的时候RPATH会被掉过(SKIP)。

3.5 总结

  • -L 用于链接时指定显示链接的库的搜索路径(优先级高)
  • -rpath 用于在链接时指定直接或间接链接的库搜索路径(最高优先级),并且(写入二进制文件中RPATH)指定运行时的本二进制文件的直接或间接依赖的动态加载库搜索路径(最高优先级)。注:有些系统默认开启链接选项-enable-new-dtags,导致-rpath生成RUNPATH。通过指定链接选项-disable-new-dtags来使其生成RPATH
  • -rpath-link 用于在链接时指定直接或间接链接的库搜索路径(优先级高)。
  • LD_LIBRARY_PATH运行时搜索直接或间接依赖。优先级低于RPATH为第二优先级
  • RUNPATH写入在二进制文件中,用于指定运行时本二进制文件的直接依赖动态加载库搜索路径(优先级低于LD_LIBRARY_PATH)。存在时覆盖二进制文件中RPATH

三、Linux 的共享库(Shared Library)

一文搞懂动态链接库的各种路径的意义与设置

Linux 下的共享库就是普通的 ELF 共享对象。

1. 相关介绍

1.1 命名

libname.so.x.y.z

  • x:主版本号,不同主版本号的库之间不兼容,需要重新编译;
  • y:次版本号,高版本号向后兼容低版本号;
  • z:发布版本号,不对接口进行更改,完全兼容。

1.2 路径

大部分包括 Linux 在内的开源系统遵循 FHS(File Hierarchy Standard)的标准,这标准规定了系统文件如何存放,包括各个目录结构、组织和作用。

  • /lib:存放系统最关键和最基础的共享库,如动态链接器、C 语言运行库、数学库等;
  • /usr/lib:存放非系统运行时所需要的关键性的库,主要是开发库;
  • /usr/local/lib:存放跟操作系统本身并不十分相关的库,主要是一些第三方应用程序的库。

动态链接器会在 /lib/usr/lib 和由 /etc/ld.so.conf 配置文件指定的,目录中查找共享库。

1.3 环境变量

  • LD_LIBRARY_PATH:临时改变某个应用程序的共享库查找路径,而不会影响其他应用程序;
  • LD_PRELOAD:指定预先装载的一些共享库甚至是目标文件;
  • LD_DEBUG:打开动态链接器的调试功能。

2. so 共享库的编写

创建一个名为 MySharedLib 的共享库。

2.1 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(MySharedLib)

set(CMAKE_CXX_STANDARD 11)

add_library(MySharedLib SHARED library.cpp library.h)

2.2 library.h

#ifndef MYSHAREDLIB_LIBRARY_H
#define MYSHAREDLIB_LIBRARY_H

// 打印 Hello World!
void hello();

// 使用可变模版参数求和
template 
T sum(T t)
{
    return t;
}
template 
T sum(T first, Types ... rest)
{
    return first + sum(rest...);
}

#endif

2.3 library.cpp

#include 
#include "library.h"

void hello() {
    std::cout << "Hello, World!" << std::endl;
}

2.4 编译生成MySharedLib.so

编译完成后,会生产 MySharedLib.so 库文件。

cd MySharedLib
mkdir build && cd build
cmake ..
make

cmake详细教程(经验版)_第1张图片

3. so 共享库的使用

创建一个名为 TestSharedLib 的可执行文件,调用 MySharedLib.so共享库。

3.1 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(TestSharedLib)

# C++11 编译
set(CMAKE_CXX_STANDARD 11)

# 头文件路径
set(INC_DIR /home/yoyo/MyDocuments/C++Projects/MySharedLib)
# 库文件路径
set(LIB_DIR /home/yoyo/MyDocuments/C++Projects/MySharedLib/build)

include_directories(${INC_DIR})
link_directories(${LIB_DIR})
link_libraries(MySharedLib)

add_executable(TestSharedLib main.cpp)

# 链接 MySharedLib 库
target_link_libraries(TestSharedLib MySharedLib)

3.2 main.cpp

#include 
#include "library.h"
using std::cout;
using std::endl;

int main() {

    hello();
    cout << "1 + 2 = " << sum(1,2) << endl;
    cout << "1 + 2 + 3 = " << sum(1,2,3) << endl;

    return 0;
}

3.3 编译项目

编译完成后,会生产 TestSharedLib 可执行文件。

cd TestSharedLib
mkdir build && cd build
cmake ..
make

3.4 执行结果

执行 TestSharedLib 可执行文件。

Hello, World!
1 + 2 = 3
1 + 2 + 3 = 6

cmake详细教程(经验版)_第2张图片

四、CMake编译

1. 问题引入

既然我们有了MakeFile,又为什么需要Cmake工具?对于不同环境下的编译,有着多种Make工具,如下所示:

  • GNU Make
  • QT 的 qmake
  • 微软的 MS nmake
  • BSD Make(pmake)
  • Makepp

这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile,这将是一件让人抓狂的工作。

CMake 就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等 [1]。

对于深度学习框架而言,跨平台是一件非常重要的事,因为深度学习模型可能在不同的环境下运行,可能是x86的Linux或者Windows,也可能是ARM,涉及到跨平台交叉编译。

2. CMake流程

在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

  1. 编写 CMake 配置文件 CMakeLists.txt 。
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmakecmake 的区别在于前者提供了一个交互式的界面)。其中, PATH 是 CMakeLists.txt 所在的目录。
  3. 使用 make 命令进行编译。

2.1 单文件CmakeLists

单文件CmakeLists,执行命令后会编译一个名为Demo的 exe文件。

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (mindsporelite)

# 指定生成目标
add_executable(mindsporelite main.cc)

如果把add_executable改成add_library,那么会生成一个的静态或者动态库。

# 生成动态库
add_library(mindsporelite SHARED main.cc)
# 生成静态库
add_library(mindsporelite STATIC main.cc)

2.2 多文件编译

对于多文件的情况,我们可以通过在add_libraryadd_executable的目标后意义列出所有文件,如下:

# 生成动态库
add_library(mindsporelite SHARED main.cc a.cc b.cc)
# 生成静态库
add_library(mindsporelite STATIC main.cc a.cc b.cc)

但是对于一个较大的工程,一一列举会显得Cmake代码十分冗长,我们可以用set设置一个变量,包含所有需要的.cc.cpp文件:

set(LITE_SRC
        ${API_SRC}
        ${CMAKE_CURRENT_SOURCE_DIR}/common/context_util.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/file_utils.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/config_file.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/utils.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/graph_util.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/log.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/lite_utils.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/prim_util.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/common/tensor_util.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/runtime/inner_allocator.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/runtime/runtime_allocator.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/runtime/infer_manager.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/schema_tensor_wrapper.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/tensor.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/ms_tensor.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/executor.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/inner_context.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/lite_model.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/kernel_registry.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/inner_kernel.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/lite_kernel.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/lite_kernel_util.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/sub_graph_kernel.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/scheduler.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/lite_session.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/errorcode.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/cpu_info.cc
        )

# 生成动态库
add_library(mindsporelite SHARED ${LITE_SRC})
# 生成静态库
add_library(mindsporelite STATIC ${LITE_SRC})

当然除了动态库和静态库,我们还可以选择将代码编译成未链接的.o中间文件,在add_library使用OBJECT参数,使用方法如下:

add_library( OBJECT [...])
add_library(... $ ...)
add_executable(... $ ...)

对于生成的中间产物,这些文件并未被链接,所以并不能作为库或执行,我们对这些中间产物还可以进行如add_dependencies的操作,add_dependencies()会为顶层目标添加一个依赖关系,可以保证某个目标在其他的目标之前被构建。比如mindsporelite依赖于flatbuffers(一个谷歌开源的序列化库)生成的fbs文件。

ms_build_flatbuffers_lite(FBS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/schema/ fbs_src ${CMAKE_BINARY_DIR}/schema "")

# 生成中间文件
add_library(lite_src_mid OBJECT ${LITE_SRC})
# 添加依赖,lite_src_mid 依赖 fbs_src
add_dependencies(lite_src_mid fbs_src)

# 生成动态库
add_library(mindsporelite SHARED $)
# 生成静态库
add_library(mindsporelite STATIC $)

2.3 生成多个库或者可执行文件

有时我们希望在一个项目中能编译多个独立的库或者可执行文件,比如在mindsporelite中,我们总共会生成以下可执行文件和动静态库

  • Mindsporelite
    • runtime
      • libminddata-lite.a (数据加载静态库)
      • libmindspore-lite-train.a(端侧训练静态库)
      • libmindspore-lite.a (端侧推理静态库)
      • libminddata-lite.so (数据加载动态库)
      • libmindspore-lite-train.so(端侧训练动态库)
      • libmindspore-lite.so (端侧推理动态库)
    • tools
      • benchmark(基准测试·工具)
      • cropper(裁剪工具)
      • converter(模型转化工具)
      • benchmark_train(训练基准测试工具)
      • codegen(代码生成工具)

而对于每个库或者可执行文件,一般在其相关的.cc文件目录下有一个CmakeLists文件,在最外侧目录的CmakeLists中通过add_subdirectory命令,指明本项目包含一个子目录 ,子目录也包含 CMakeLists.txt 文件,这样子目录下的 CMakeLists.txt 文件和源代码也会被处理。通过多层的CmakeLists我们一一构建各层的库和依赖。以下代码表示添加一个src的子目录。

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)

2.4 链接库

add_subdirectory往往和target_link_libraries一起使用,在我们编译好一个可执行文件或者库时,如果它依赖其他库,我们可以使用target_link_libraries将其链接其他库,方法如下:

# 生成动态库
add_library(mindsporelite SHARED $)
# 生成静态库
add_library(mindsporelite-static STATIC $)
# 生成可执行文件
add_executable(benchmark main.cc)

# 动态链接,将 benchmark 链接到 mindsporelite
# mindsporelite中的 `benchmark ` 基准测试工具依赖于 `mindsporelite` runtime库
target_link_libraries(benchmark mindsporelite)

#静态链接
target_link_libraries(benchmark mindsporelite-static)

链接又分为动态链接和静态链接,静态链接会将库中所有的代码一起编译到可执行文件中,运行时速度更快,但包的大小更大,动态链接不会将库的代码编译到可执行文件中,文件更小,但在运行时会搜索动态库,运行速度慢。如果在系统目录和环境变量中找不到动态库,那么在运行时会报错,在Linux环境中,可以通过设置环境变量LD_LIBRARY_PATH指定动态库目录:

export LD_LIBRARY_PATH=/path/to/lib:${LD_LIBRARY_PATH}

这里有在链接库时,Cmake是如何找到对应库的位置的?在Cmake中,我们一般在文件开始添加 include_directories(包含指定目录)或aux_source_directory(包含所有子目录)命令,Cmake会在这些目录下进行搜索。

3. CMake编译安装OpenCV

NNIE模型转换环境搭建

3.1 下载OpenCV

OpenCV - https://opencv.org/
opencv_contrib - github

3.2 安装依赖

sudo apt-get install build-essential
sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libtiff-dev libjasper-dev libdc1394-22-dev

3.3 编译安装

tar -xvzf opencv-3.4.0.tar.gz

cd opencv-3.4.0

3.4 配置opencv_contrib

如果编译过程出现问题,就不编译opencv_contrib即可

mv …/opencv_contrib-3.4.0 ./

3.5 cmake生成MakeFile

mkdir build

mkdir install

cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/x/x \
–D WITH_VTK=ON \
-D OPENCV_EXTRA_MODULES_PATH=/x/x/opencv_contrib-3.4.0/modules/ \
-D CUDA_NVCC_FLAGS="-std=c++11 --expt-relaxed-constexpr" \
-D WITH_NVCUVID=OFF \
-D BUILD_opencv_cudacodec=OFF \
-D ENABLE_CXX11=YES  \
..

参数解释:

  • OPENCV_EXTRA_MODULES_PATHopencv_contrib/modules的路径;
  • CMAKE_INSTALL_PREFIX,安装的路径;
  • cuda10以上没有 dynlink_nvcuvid.h nvcuvid.h,所以要将 BUILD_opencv_cudacodec=OFF。如果编译 opencv-contrib 需要下载boost之类的可以不编译这个,即去掉OPENCV_EXTRA_MODULES_PATH。

3.6 编译安装

# 编译
make -j${nproc}

make check

# 安装
make install

4. 编译安装 protobuf

NNIE模型转换环境搭建

4.1 下载protobuf

下载地址

4.2 编译安装

tar -xvf protobuf

cd protobuf
autogen.sh

configure -prefix=/you/want/to/install/

make
make check
make install

4.3 配置环境变量

vim ~/.bashrc

export LD_LIBRARY_PATH=/PATH/TO/lib:$LD_LIBRARY_PATH
export PATH=/PATH/TO/bin:$PATH

五、CMake常用操作

1. ENV环境变量

CMake语法—环境变量(Environment Variable)

1.1 定义格式

set(ENV{} [])

参数解释

  • ENV:环境变量标志性前缀;
  • variable:变量名称;
  • value:变量值;

示例

# 定义环境变量
set(ENV{CMAKE_PATH} "F:/cmake")
 
# 判断CMAKE_PATH环境变量是否定义
if(DEFINED ENV{CMAKE_PATH})
    message("CMAKE_PATH_1: $ENV{CMAKE_PATH}")
else()
    message("NOT DEFINED CMAKE_PATH VARIABLES")
endif()

1.2 示例

cmake_minimum_required(VERSION 3.18)
 
# 设置工程名称
set(PROJECT_NAME KAIZEN)
 
# 设置工程版本号
set(PROJECT_VERSION "1.0.0.10" CACHE STRING "默认版本号")
 
# 工程定义
project(${PROJECT_NAME}
    LANGUAGES CXX C
    VERSION ${PROJECT_VERSION}
)
 
# 打印开始日志
message(STATUS "\n########## BEGIN_TEST_ENV_VARIABLE")
 
# 判断JAVA_HOME变量是否定义
if(DEFINED ENV{JAVA_HOME})
    message("JAVA_HOME: $ENV{JAVA_HOME}")
else()
    message("NOT DEFINED JAVA_HOME VARIABLES")
endif()
 
# 定义环境变量
set(ENV{CMAKE_PATH} "F:/cmake")
 
# 判断CMAKE_PATH环境变量是否定义
if(DEFINED ENV{CMAKE_PATH})
    message("CMAKE_PATH_1: $ENV{CMAKE_PATH}")
else()
    message("NOT DEFINED CMAKE_PATH VARIABLES")
endif()
 
# 定义测试函数,在函数中新定义环境变量
function(test_env_variable)
    # 访问环境变量CMAKE_PATH
    message("CMAKE_PATH_2: $ENV{CMAKE_PATH}")
    
    # 函数内定义环境变量
    set(ENV{CMAKE_FUNC} "F:/cmake/dir")
 
    # 判断CMAKE_FUNC环境变量是否定义
    if(DEFINED ENV{CMAKE_FUNC})
        message("CMAKE_FUNC_1: $ENV{CMAKE_FUNC}")
    else()
        message("NOT DEFINED CMAKE_FUNC_1 VARIABLES")
    endif()
endfunction()
 
# 调用函数
test_env_variable()
 
# 判断CMAKE_FUNC环境变量是否定义
if(DEFINED ENV{CMAKE_FUNC})
    message("CMAKE_FUNC_2: $ENV{CMAKE_FUNC}")
else()
    message("NOT DEFINED CMAKE_FUNC_2 VARIABLES")
endif()
 
# 如果没有参数值
set(ENV{CMAKE_FUNC})
 
# 判断CMAKE_FUNC环境变量是否定义
if(DEFINED ENV{CMAKE_FUNC})
    message("CMAKE_FUNC_3: $ENV{CMAKE_FUNC}")
else()
    message("NOT DEFINED CMAKE_FUNC_3 VARIABLES")
endif()
 
# 定义测试宏,在函数中新定义环境变量
macro(test_env_var)
    # 访问环境变量CMAKE_PATH
    message("CMAKE_PATH_3: $ENV{CMAKE_PATH}")
    
    # 宏内定义环境变量
    set(ENV{CMAKE_MACRO} "F:/cmake/macro")
 
    # 判断CMAKE_MACRO环境变量是否定义
    if(DEFINED ENV{CMAKE_MACRO})
        message("CMAKE_MACRO_1: $ENV{CMAKE_MACRO}")
    else()
        message("NOT DEFINED CMAKE_MACRO_1 VARIABLES")
    endif()
endmacro()
 
# 调用宏
test_env_var()
 
# 判断CMAKE_MACRO环境变量是否定义
if(DEFINED ENV{CMAKE_MACRO})
    message("CMAKE_MACRO_2: $ENV{CMAKE_MACRO}")
else()
    message("NOT DEFINED CMAKE_MACRO_2 VARIABLES")
endif()
 
# 如果多个参数值
set(ENV{CMAKE_FILE} "F:/cmake/cmake1.txt" "F:/cmake/cmake2.txt")
 
# 判断CMAKE_FILE环境变量是否定义
if(DEFINED ENV{CMAKE_FILE})
    message("CMAKE_FILE: $ENV{CMAKE_FILE}")
else()
    message("NOT DEFINED CMAKE_FILE VARIABLES")
endif()
 
# 打印结束日志
message(STATUS "########## END_TEST_ENV_VARIABLE\n")

输出结果

-- Selecting Windows SDK version 10.0.18362.0 to target Windows 10.0.17763.
-- The CXX compiler identification is MSVC 19.0.24245.0
-- The C compiler identification is MSVC 19.0.24245.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/amd64/cl.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
--
########## BEGIN_TEST_ENV_VARIABLE
JAVA_HOME: C:\Program Files\Java\jdk1.8.0_201
CMAKE_PATH_1: F:/cmake
CMAKE_PATH_2: F:/cmake
CMAKE_FUNC_1: F:/cmake/dir
CMAKE_FUNC_2: F:/cmake/dir
NOT DEFINED CMAKE_FUNC_3 VARIABLES
CMAKE_PATH_3: F:/cmake
CMAKE_MACRO_1: F:/cmake/macro
CMAKE_MACRO_2: F:/cmake/macro
CMake Warning (dev) at CMakeLists.txt:98 (set):
  Only the first value argument is used when setting an environment variable.
  Argument 'F:/cmake/cmake2.txt' and later are unused.
This warning is for project developers.  Use -Wno-dev to suppress it.
 
CMAKE_FILE: F:/cmake/cmake1.txt
-- ########## END_TEST_ENV_VARIABLE
 
-- Configuring done
-- Generating done
-- Build files have been written to: F:/learn_cmake/build
请按任意键继续. . .

2. CACHE缓存变量

CMake语法—缓存变量(Cache Variable)

2.1 缓存变量简介

  • Normal Variable,普通变量,相当于一个局部变量。在同一个CMake工程中使用,会有作用域限制或区分。

  • Cache Variable,缓存变量,相当于一个全局变量。在同一个CMake工程中任何地方都可以使用。

2.2 定义格式

set( ... CACHE   [FORCE])

参数解释

  • variable:变量名称;
  • value:变量值列表;
  • CACHE:cache变量的标志;
  • type:变量类型,取决于变量的值。类型分为:BOOL、FILEPATH、PATH、STRING、INTERNAL;
  • docstring:必须是字符串,作为变量概要说明;
  • FORCE:强制选项,强制修改变量值;

示例

set(MSLITE_REGISTRY_DEVICE "off" CACHE STRING "Compile Mindspore Lite that supports specific devices, currently supported devices: Hi3516D/Hi3519A/Hi3559A/SD3403")

2.3 注意事项

  1. 缓存变量,本质是全局变量,可以把缓存变量当做C、C++中的全局变量理解即可。类比法理解与体会,更易于学习与应用。
  2. 缓存变量,都会存储在CMakeCache.txt文件中,当你确认某个变量是缓存变量时,理论上你一定可以在CMakeCache.txt中找到此变量的记录项。
  3. CMakeCache.txt文件中,还有很多默认的缓存变量,可自行查看与分析研究。
  4. 缓存变量发生问题,一定记得先删除build目录下的CMakeCache.txt文件,然后重新配置项目。

3. cmake关系操作符

操作符 含义
DEFINED ~ E,变量被定义了,真
NOT 非,NOT E1
AND 与,E1 AND E2
OR 或,E1 OR E2
EXIST ~ E,存在 name 的文件或者目录(应该使用绝对路径),真
COMMAND ~ E,存在 command-name 命令、宏或函数且能够被调用,真
EQUAL E1 ~ E2,变量值或者字符串匹配 regex 正则表达式
LESS E1 ~ E2,变量值或者字符串匹配 regex 正则表达式
GREATER E1 ~ E2,变量值或者字符串匹配 regex 正则表达式
STRLESS E1 ~ E2,变量值或者字符串为有效的数字且满足小于的条件
STRGREATER E1 ~ E2,变量值或者字符串为有效的数字且满足大于的条件
STREQUAL E1 ~ E2,变量值或者字符串为有效的数字且满足等于的条件

STREQUAL

STREQUAL 用于比较字符串,相同返回 true

if(NOT("${X86_64_SIMD}" STREQUAL "sse" OR "${X86_64_SIMD}" STREQUAL "avx" OR "${X86_64_SIMD}" STREQUAL "avx512"))
    set(KERNEL_SRC_SSE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/fp32/convolution_im2col_sse_fp32.cc
                            ${CMAKE_CURRENT_SOURCE_DIR}/fp32/matmul_fp32_sse.cc
                            ${CMAKE_CURRENT_SOURCE_DIR}/fp32/convolution_winograd_sse_fp32.cc
    )
    list(REMOVE_ITEM KERNEL_SRC ${KERNEL_SRC_SSE_FILE})
endif()

4. .cmake文件

cmake(三十五)Cmake之include指令

CMake中include的使用

.cmake文件是一个模块(module)文件,可以被 includeCMakeLists.txt 中。

CMakeLists.txt 包含该 .cmake文件 时,当编译运行时,该 .cmake 里的一些命令就会在该 include包含处 得到加载 执行,在后续能够调用该 .cmake 里的一些宏和函数。

include(${CMAKE_SOURCE_DIR}/cmake/options.cmake)

4.1 OPTIONAL选项

如果指定了 OPTIONAL,即使文件不存在也不会触发error。

# xxxx.cmake不存在也不会触发warning或error
include(xxxx.cmake OPTIONAL) 

# xxxx.cmake不存在,会触发error
# CMake Error at test_include.cmake:9 (include):
#   include could not find requested file: xxxx.cmake
include(xxxx.cmake)

4.2 RESULT_VARIABLE选项

如果给定了 RESULT_VARIABLE,变量 将被设置为已包含的完整的文件名,如果没有找到且指定了OPTIONAL 则为 NOTFOUND

include(test_project.cmake RESULT_VARIABLE var)

# var: /home/spring/GitHub/Linux_Code_Test/Samples_CMake/messy_usage/test_project.cmake
message("var: ${var}") 

# xxxx.cmake不存在
include(xxxx.cmake OPTIONAL RESULT_VARIABLE var) 

# var: NOTFOUND
message("var: ${var}") 

# xxxx.cmake不存在,触发error
# CMake Error at test_include.cmake:20 (include):
#   include could not find requested file: xxxx.cmake
include(xxxx.cmake RESULT_VARIABLE var) 

注意

  • .cmake不要包含工程之类的信息,例如:add_subdirectory()CMAKE_CURRENT_ 之类的。
  • .cmake 不要调用 CMakeLists.txt
  • 一般情况系,.cmake文件放在cmake目录下,路径的前缀为:CMAKE_CURRENT_SOURCE_DIR/cmake/,建议为绝对路径

5. cmake指定Python版本(FindPython3)

FindPython3 — CMake 3.25.0 Documentation

C++ CMake 使用 Python3

5.1 python3属性

如果 find_package() 找到了python3包,对应的python3会包含以下属性。

名称 说明
Python3_Found 系统具有 Python3 需要的组件
Python3_Interpreter_Found 系统具有 Python3 解释器
Python3_EXECUTABLE Python3 解释器的路径
Python3_INTERPRETER_ID 解释器名称的唯一标识,可能是 PythonActivePythonAnacondaCanopyIronPython之一
Python3_STDLIB 标准平台独立安装的目录。可以通过 distutils.sysconfig.get_python_lib(plat_specific=False, standard_lib=True) 获取信息
Python3_STDARCH 标准平台依赖安装的目录。可以通过 distutils.sysconfig.get_python_lib(plat_specific=True,standard_lib=True) 获取信息
Python3_SOABI 模块的扩展名后缀。可以通过 distutils.sysconfig.get_config_flag('SOABI')distutils.sysconfig.get_config_flag('EXT_SUFFIX')python3-config --extension-suffix 获取信息
Python3_Compiler_FOUND 系统具有 Python3 编译器
Python3_COMPILER Python3 编译器的路径,只有使用 IronPython 时提供
Python3_COMPILER_ID 编译器名称的唯一标识,可能是 IronPython
Python3_Development_FOUND 系统具有 Python3 开发环境套件
Python3_INCLUDE_DIRS Python3 include 文件目录
Python3_LIBRARIES Python3 库文件
Python3_LIBRARY_DIRS Python3 库文件路径
Python3_RUNTIME_LIBRARY_DIRS Python3 运行时库文件路径
Python3_VERSION Python3 版本
Python3_VERSION_MAJOR Python3 主版本
Python3_VERSION_MINOR Python3 此版本
Python3_VERSION_PATCH Python3 小版本
Python3_NumPy_FOUND 系统具有 Numpy
Python3_NumPy_INCLUDE_DIRS NumPy include 文件目录
Python3_NumPy_VERSION NumPy 版本

5.2 示例一(推荐)

# 如果使用的是非系统目录下的 Python 可以通过指定 Python3_ROOT_DIR 改变查找路径
set(Python3_ROOT_DIR "/home/liulinjun/miniconda3/envs/mslite")

# 寻找python3
find_package(Python3 COMPONENTS Interpreter Development)
if(Python3_FOUND)
    set(PYTHON_INCLUDE_DIRS "${Python3_INCLUDE_DIRS}")
    set(PYTHON_LIBRARIES "${Python3_LIBRARIES}")
    
    # 寻找numpy组件
    find_package(Python3 COMPONENTS NumPy Development)
	
	# 如果找到numpy组件
    if(Python3_NumPy_FOUND)
        include_directories(${Python3_INCLUDE_DIRS})
        include_directories(${Python3_NumPy_INCLUDE_DIRS})
        include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../)
        include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../core/)
        include(${TOP_DIR}/cmake/external_libs/pybind11.cmake)
     endif()
else()
    find_python_package(py_inc py_lib)
    set(PYTHON_INCLUDE_DIRS "${py_inc}")
    set(PYTHON_LIBRARIES "${py_lib}")
endif()

# python的include路径
message("PYTHON_INCLUDE_DIRS = ${PYTHON_INCLUDE_DIRS}")
# python的lib路径
message("PYTHON_LIBRARIES = ${PYTHON_LIBRARIES}")
# python版本
message("PYTHON_VERSION = ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}.${Python3_VERSION_PATCH}")

# 包含头文件
include_directories(${PYTHON_INCLUDE_DIRS})

参数解释

  • Interpreter:寻找python3解释器;
  • Compiler:寻找python3编译器,仅使用 IronPython 时提供;
  • Development:寻找开发环境套件(包含 includelib 目录);
  • Numpy:寻找 Numpy 组件;

如果没有指定 COMPONENT,默认使用 Interpreter

为了确保所有组件 InterpreterCompilerDevelopmentNumpy的版本一致,需要同时指定所有组件,如下指令:

find_package(Python3 COMPONENTS Interpreter Development)

注意:如果 InterpreterDevelopment 组件都被指定的话,这个模块只会搜索 Cmake 配置的平台架构的解释器。如果仅指定了 Interpreter 组件的话,这个约束不会生效。

5.3 示例二

cmake_minimum_required(VERSION 3.10)
project(c_matrix C CXX)

# 增加 Python3_INCLUDE_DIRS
add_definitions(-D Python3_INCLUDE_DIRS=/home/liulinjun/miniconda3/envs/mslite/include)
# 增加 Python3_LIBRARIES
add_definitions(-D Python3_LIBRARIES=/home/liulinjun/miniconda3/envs/mslite/lib)

find_package(Python3 COMPONENTS Interpreter Development)
if (Python3_FOUND)
    message("Python include directory: " ${Python3_INCLUDE_DIRS})
    message("Python version is: " ${Python3_VERSION})
    include_directories(${ Python3_INCLUDE_DIRS})
    target_link_libraries(main ${ Python3_LIBRARIES})
endif (Python3_FOUND)
-- Found PythonInterp: /home/liulinjun/miniconda3/envs/mslite/bin/python (found suitable version "3.8.13", minimum required is "3.6")
-- Found PythonLibs: /home/liulinjun/miniconda3/envs/mslite/lib/libpython3.8.so

5.4 示例三

cmake -DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython2.7.so \
    -DPYTHON_INCLUDE_DIR=/usr/include/python2.7 \
    -DPYTHON_EXECUTABLE=/usr/bin/python2.7 \
    ..
-- Found PythonInterp: /usr/bin/python2.7 (found version "2.7.17") 
-- Found PythonLibs: /usr/lib/x86_64-linux-gnu/libpython2.7.so=

6. CPack生成安装包

如果想要生成安装包,则需要使用 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)

参数解释

  • include(InstallRequiredSystemLibraries):导入 InstallRequiredSystemLibraries 模块。
  • 设置一些 CPack 相关变量。
  • include(CPack):导入 CPack 模块。

接着执行 cmakemake 构建工程,此时再执行 cpack 命令即可生成安装包:

#生成二进制安装包
cpack -C CPackConfig.cmake

#生成源码安装包
cpack -C CPackSourceConfig.cmake

当命令执行成功后,就会在当前目录下生成 .sh.tar.gz.tar.Z 这三个格式的安装包。

7. 支持 gdb

# 启动debug模式
set(CMAKE_BUILD_TYPE "Debug")

# 开启 -g 选项
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

六、CMake编译工程

CMake目录结构:项目主目录存在一个CMakeLists.txt文件。

1. 编译规则

两种方式设置编译规则

  1. 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory添加子目录即可
  2. 包含源文件的子文件夹未包含CMakeLists.txt文件,子目录编译规则体现在主目录的CMakeLists.txt中

2. 编译流程

在linux平台下使用CMake构建C/C++工程的流程

  1. 编写 CMake 配置文件 CMakeLists.txt
  2. CMakeLists.txt 文件所在目录创建一个 build 文件夹,然后进入目录。(这一步可以省略,但是生成的中间文件不易清理)
  3. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmakecmake 的区别在于前者提供了一个交互式的界面)。其中, PATHCMakeLists.txt 所在的目录。
  4. 使用 make 命令进行编译,使用 make install 进行安装。

3. 构建方式

3.1 外部构建(推荐使用)

外部构建(out-of-source build),将编译输出文件和源文件放到不同的目录中

# 外部构建
# 1. 在当前目录下,创建build文件夹
mkdir build

# 2. 进入到build文件夹
cd build

# 3. 编译上级目录的CMakeLists.txt,生成Makefile和其他文件
cmake ..

# 4. 执行make命令,生成target
make

3.2 内部构建(不推荐)

内部构建(in-source build)会在同级目录下产生一大堆中间文件,这些中间文件和工程源文件放在一起显得杂乱无章

# 内部构建
# 在当前目录下,编译本目录的CMakeLists.txt,生成Makefile和其他文件
cmake .

# 执行make命令,生成target
make

4. cmake构建大型C++项目

CMake用法示例

对于任何跨平台的Cpp项目,其Cmake的架构基本大同小异,按层级结构一一编译各个动静态库,最后链接成一个可执行文件或者库。

5. cmake例程

(有空学习一下,亲自尝试编写cmake代码)

cmake-examples

cmake-demo

你可能感兴趣的:(运维,cmake,MakeFile,CMakeLists,c++)