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)
CMake中常用的预定义变量
项目示例
D:\MyDocuments\cache\Demo>tree
.
├─build
│ ├─bin
│ └─lib
├─example
└─src├─base├─http└─net
PROJECT_NAME
通过PROJECT指定的项目名称:
project(Demo)
PROJECT_SOURCE_DIR
工程的根目录,项目示例中的Demo
目录。
PROJECT_BINARY_DIR
执行cmake
命令的目录,一般是在build
目录,在此目录执行cmake ..
CMAKE_CURRENT_SOURCE_DIR
当前CMakeLists.txt文件所在目录。
CMAKE_CURRENT_BINARY_DIR
编译目录,可使用ADD_SUBDIRECTORY
来修改此变量。
# 添加cmake执行子目录
ADD_SUBDIRECTORY(example)
EXECUTABLE_OUTPUT_PATH
二进制可执行文件输出位置。
# 设置可执行文件的输出路径为 build/bin
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
LIBRARY_OUTPUT_PATH
库文件输出位置。
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
liulinjun@LAPTOP-4DTD5D42:~$ cmake --version
cmake version 3.11.2
CMAKE_MAJOR_VERSION
: cmake的主版本号cmake version 3.11.2
中的3
;
CMAKE_MINOR_VERSION
: cmake的次版本号cmake version 3.11.2
中的11
;
CMAKE_PATCH_VERSION
: cmake的补丁等级cmake version 3.11.2
中的2
;
CMAKE_SYSTEM
: 系统名称,带版本号;
CMAKE_SYSTEM_NAME
: 系统名称,不带版本号;
CMAKE_SYSTEM_VERSION
: 系统版本号;
CMAKE_SYSTEM_PROCESSOR
: 处理器名称;
BUILD_SHARED_LIBS
默认的库编译方式(shared
or static
),默认为static
,一般在ADD_LIBRARY
时直接指定编译库的类型。
CMAKE_C_FLAGS
设置C编译选项。
SET(CMAKE_C_FLAGS_PUBLIC "-mcpu=cortex-a7 -mfloat-abi=softfp -mfpu=neon-vfpv4 -ffunction-sections -mno-unaligned-access -fno-aggressive-loop-optimizations -mapcs-frame -rdynamic")
SET(CMAKE_C_FLAGS_DEBUG "-Wall -ggdb3 -DNM_DEBUG ${CMAKE_C_FLAGS_PUBLIC}")
SET(CMAKE_C_FLAGS_RELEASE "-Wall -O3 ${CMAKE_C_FLAGS_PUBLIC}")
CMAKE_CXX_FLAGS
设置C++编译选项。
CMAKE_CXX_FLAGS_DEBUG
: 设置编译类型为Debug时的编译选项;CMAKE_CXX_FLAGS_RELEASE
: 设置编译类型为Release时的编译选项;CMAKE_CXX_COMPILER
设置C++编译器。
# 设置C++编译器为g++
set(CMAKE_CXX_COMPILER "g++")
# 设置标准库版本为c++17 并开启警告
set(CMAKE_CXX_FLAGS "-std=c++17 -Wall")
# 设置Debug模式下,不开启优化,开启调试,生成更详细的gdb调试信息
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -ggdb")
# 设置Release模式下,开启最高级优化
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
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()
CMake语法—缓存变量(Cache Variable)
Normal Variable,普通变量,相当于一个局部变量。在同一个CMake工程中使用,会有作用域限制或区分。
Cache Variable,缓存变量,相当于一个全局变量。在同一个CMake工程中任何地方都可以使用。
set( ... CACHE [FORCE])
参数解释
示例
set(MSLITE_REGISTRY_DEVICE "off" CACHE STRING "Compile Mindspore Lite that supports specific devices, currently supported devices: Hi3516D/Hi3519A/Hi3559A/SD3403")
CMake语法—环境变量(Environment Variable)
set(ENV{} [])
参数解释
示例
# 定义环境变量
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()
CMakeLists.txt
示例cmake_minimum_required(VERSION 3.18)# 设置工程名称
set(PROJECT_NAME KAIZEN)# 设置工程版本号
set(PROJECT_VERSION "1.0.0.10" CACHE STRING "默认版本号")# 工程定义
project(${PROJECT_NAME}LANGUAGES CXX CVERSION ${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_PATHmessage("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_PATHmessage("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
请按任意键继续. . .
对于单文件"hello.cpp",
生成hello(hello.exe)所需要执行的bash命令:
gcc -v -o hello hello.c
但当涉及多文件编译时,我们的命令会十分复杂,比如一个main.cpp
文件依赖于Foo1.cpp
,Foo2.cpp
,…Foo10
,我们需要先将这10个文件编译成.o
文件,再将各.o
文件和main.o
进行链接,代码如下:
g++ -std=c++17 -O2 -o Foo1.o -c Foo1.cpp
g++ -std=c++17 -O2 -o Foo2.o -c Foo2.cpp
g++ -std=c++17 -O2 -o Foo3.o -c Foo3.cpp
g++ -std=c++17 -O2 -o Foo4.o -c Foo4.cpp
g++ -std=c++17 -O2 -o Foo5.o -c Foo5.cpp
g++ -std=c++17 -O2 -o Foo6.o -c Foo6.cpp
g++ -std=c++17 -O2 -o Foo7.o -c Foo7.cpp
g++ -std=c++17 -O2 -o Foo8.o -c Foo8.cpp
g++ -std=c++17 -O2 -o Foo9.o -c Foo9.cpp
g++ -std=c++17 -O2 -o Foo10.o -c Foo10.cpp
g++ -std=c++17 -O2 -o main.o -c main.cpp
g++ -std=c++17 -O2 main.o Foo1.o Foo2.o Foo3.o Foo4.o Foo5.o Foo6.o Foo7.o Foo8.o Foo9.o Foo10.o -o main
上述编译过程还算简单,因为各子文件还不相互依赖,但如果子文件有相互依赖关系,比如foo1.cpp
依赖于foo10.cpp
我们编译的顺序有必须更改,在文件数目更大的工程中,一行一行输入命令进行编译是不现实的,所以我们引入了Makefile,对于上述编译任务,我们的MakeFile如下,我们只需输入make
一句命令即可完成编译。
COMPILER = g++ -std=c++17 -O2 -I./include
OBJECTS = Foo1.o Foo2.o Foo3.o Foo4.o Foo5.o Foo6.o Foo7.o Foo8.o Foo9.o Foo10.o main.o
TARGET = main$(TARGET): $(OBJECTS)$(COMPILER) -o $@ $^ -lprotobuf
$(OBJECTS): %.o: %.cpp$(COMPILER) -o $@ -c $<
$(DEPENDENCIES): %.d: %.cpp$(COMPILER) -o $@ -MM $
Makefile 规定了一套编译规则,使用什么编译器,编译器使用什么样的编译选项,每个文件都有什么依赖关系。在编译的时候,能够直接根据 makefile 来确定哪些文件依赖于其他的哪些文件,从而把编译顺序、需不需要重新编译以及链接都自动检测出来。在Linux环境下,MakeFile本身可以看做一个shell脚本。
既然我们有了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,涉及到跨平台交叉编译。
在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:
cmake PATH
或者 ccmake PATH
生成 Makefile(ccmake
和 cmake
的区别在于前者提供了一个交互式的界面)。其中, PATH
是 CMakeLists.txt 所在的目录。make
命令进行编译。单文件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)
对于多文件的情况,我们可以通过在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( 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})
#添加依赖
add_dependencies(lite_src_mid fbs_src)# 生成动态库
add_library(mindsporelite SHARED $)
# 生成静态库
add_library(mindsporelite STATIC $)
有时我们希望在一个项目中能编译多个独立的库或者可执行文件,比如在mindsporelite
中,我们总共会生成以下可执行文件和动静态库
而对于每个库或者可执行文件,一般在其相关的.cc
文件目录下有一个CmakeLists
文件,在最外侧目录的CmakeLists
中通过add_subdirectory
命令,指明本项目包含一个子目录 ,子目录也包含 CMakeLists.txt 文件,这样子目录下的 CMakeLists.txt 文件和源代码也会被处理。通过多层的CmakeLists
我们一一构建各层的库和依赖。以下代码表示添加一个src
的子目录。
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)
add_subdirectory
往往和target_link_libraries
一起使用,在我们编译好一个可执行文件或者库时,如果它依赖其他库,我们可以使用target_link_libraries
将其链接其他库,方法如下:
# 生成动态库
add_library(mindsporelite SHARED $)
# 生成静态库
add_library(mindsporelite-static STATIC $)
# 生成可执行文件
add_executable(benchmark main.cc)
# 动态链接
target_link_libraries(benchmark mindsporelite)
#静态链接
target_link_libraries(benchmark mindsporelite-static)
mindsporelite中的基准测试工具依赖于mindsporelite runtime库,链接代码如上,链接又分为动态链接和静态链接,静态链接会将库中所有的代码一起编译到可执行文件中,运行时速度更快,但包的大小更大,动态链接不会将库的代码编译到可执行文件中,文件更小,但在运行时会搜索动态库,运行速度慢。如果在系统目录和环境变量中找不到动态库,那么在运行时会报错,在Linux环境中,可以通过设置环境变量LD_LIBRARY_PATH
指定动态库目录:
export LD_LIBRARY_PATH=/path/to/lib:${LD_LIBRARY_PATH}
这里有在链接库时,Cmake是如何找到对应库的位置的?在Cmake中,我们一般在文件开始添加 include_directories(包含指定目录)或aux_source_directory(包含所有子目录)命令,Cmake会在这些目录下进行搜索。
add_library (MathFunctions ${DIR_LIB_SRCS})
NNIE模型转换环境搭建
OpenCV - https://opencv.org/
opencv_contrib - github
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
tar -xvzf opencv-3.4.0.tar.gzcd opencv-3.4.0
如果编译过程出现问题,就不编译opencv_contrib即可。
mv …/opencv_contrib-3.4.0 ./
mkdir buildmkdir installcmake -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
,安装的路径;dynlink_nvcuvid.h
和 nvcuvid.h
,所以要将 BUILD_opencv_cudacodec=OFF
。如果编译 opencv-contrib
需要下载boost之类的可以不编译这个,即去掉OPENCV_EXTRA_MODULES_PATH。make -j${nproc}make check# 安装
make install
NNIE模型转换环境搭建
下载地址
tar -xvf protobufcd protobuf
autogen.shconfigure -prefix=/you/want/to/install/make
make check
make install
把lib路径加入到LD_LIBRARY_PATH中,把bin加到PATH中(通过编辑~/.bashrc即可)。
上一篇:如何选择线程数量