一、参考资料
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.so 和 libb.so 到系统路径下,修改环境变量 LD_LIBRARY_PATH 或增加配置文件ld.so.confg 中搜索路径。这些都可能改变当前的运行环境,可能导致一些意想不到的事情发生,因此不是很好的方法。对系统影响最小的方法是,在编译的时候指定运行时动态加载库的搜索路径。与之先关的概念有:-R/-rpath/-rpath-link、rpath/runpath 。
3.2 rpath
rpath,通过 -Wl,-rpath,d1:..:dn 指定。指定的rpath被 硬编码(hard coding/hard compile) 到二进制文件中,规定了二进制文件动态加载其他库时的最优先顺序。相比最粗粒度的路径控制LD_LIBRARY_PATH,rpath可以认为是最细粒度的控制。
通过 readelf -d liba.so | grep path或readelf -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失效,且优先级低于RPATH和LD_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 <typename T> T sum(T t) { return t; } template <typename T, typename ...Types> T sum(T first, Types ... rest) { return first + sum<T>(rest...); } #endif
2.3 library.cpp
讯享网#include <iostream> #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
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 <iostream> #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编译
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 并编译的流程如下:
- 编写 CMake 配置文件 CMakeLists.txt 。
- 执行命令
cmake PATH或者ccmake PATH生成 Makefile(ccmake和cmake的区别在于前者提供了一个交互式的界面)。其中,PATH是 CMakeLists.txt 所在的目录。 - 使用
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_library或add_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(<name> OBJECT [<source>...]) add_library(... $<TARGET_OBJECTS:objlib> ...) add_executable(... $<TARGET_OBJECTS:objlib> ...)
对于生成的中间产物,这些文件并未被链接,所以并不能作为库或执行,我们对这些中间产物还可以进行如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 $<TARGET_OBJECTS:lite_src_mid>) # 生成静态库 add_library(mindsporelite STATIC $<TARGET_OBJECTS:lite_src_mid>)
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(代码生成工具)
- runtime
而对于每个库或者可执行文件,一般在其相关的.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 $<TARGET_OBJECTS:lite_src_mid>) # 生成静态库 add_library(mindsporelite-static STATIC $<TARGET_OBJECTS:lite_src_mid>) # 生成可执行文件 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
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_PATH,opencv_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{<variable>} [<value>])
参数解释
- 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(<variable> <value>... CACHE <type> <docstring> [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 注意事项
- 缓存变量,本质是全局变量,可以把缓存变量当做C、C++中的全局变量理解即可。类比法理解与体会,更易于学习与应用。
- 缓存变量,都会存储在CMakeCache.txt文件中,当你确认某个变量是缓存变量时,理论上你一定可以在CMakeCache.txt中找到此变量的记录项。
- CMakeCache.txt文件中,还有很多默认的缓存变量,可自行查看与分析研究。
- 缓存变量发生问题,一定记得先删除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)文件,可以被 include 到 CMakeLists.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,变量 <var> 将被设置为已包含的完整的文件名,如果没有找到且指定了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 | 解释器名称的唯一标识,可能是 Python、ActivePython、Anaconda、Canopy、IronPython之一 |
| 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:寻找开发环境套件(包含include和lib目录);Numpy:寻找 Numpy 组件;
如果没有指定 COMPONENT,默认使用 Interpreter。
为了确保所有组件 Interpreter,Compiler,Development,Numpy的版本一致,需要同时指定所有组件,如下指令:
find_package(Python3 COMPONENTS Interpreter Development)
注意:如果 Interpreter 和 Development 组件都被指定的话,这个模块只会搜索 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 模块。
接着执行 cmake 和 make 构建工程,此时再执行 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. 编译规则
两种方式设置编译规则
- 包含源文件的子文件夹包含CMakeLists.txt文件,主目录的CMakeLists.txt通过add_subdirectory添加子目录即可
- 包含源文件的子文件夹未包含CMakeLists.txt文件,子目录编译规则体现在主目录的CMakeLists.txt中
2. 编译流程
在linux平台下使用CMake构建C/C++工程的流程
- 编写 CMake 配置文件
CMakeLists.txt。 - 在
CMakeLists.txt文件所在目录创建一个 build 文件夹,然后进入目录。(这一步可以省略,但是生成的中间文件不易清理) - 执行命令
cmake PATH或者ccmake PATH生成 Makefile(ccmake和cmake的区别在于前者提供了一个交互式的界面)。其中,PATH是CMakeLists.txt所在的目录。 - 使用
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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/28606.html