从mxnet的项目组织结构学习cmake

说明

本系列会介绍MXNet如何使用CMake来组织项目的。

本系列文章涉及大多数实际开放中会用到CMake命令。

本文中的代码来自MXNet

CMake是什么?为什么要学习CMake?

要搞清楚CMake是干什么的,我们需要了解整个C++项目编写的发展过程。由于本人能力有限,这里只是简单介绍一下。
在代码完成编写之后需要通过编译器对代码进行编译,当项目变得庞大之后,每次编译完成之后手动输入编译命令进行编译就成了一件很麻烦的事情,为了解决每次编译需要手动输入编译命令的问题,诞生了Makefile。所谓Makefile就是将整个项目的编译命令写到一个名为Makeifle的文件中,每次只需要输入make就能够进行整个项目的编译,Makefile实现了很多方便的操作,例如一次性查找所有源文件,这样就不用每次增加一个源文件就手动写一条g++ filename.cpp -c -o filename.o命令。
虽然Makefile实现了只需要写一次编译命令,但是当项目变得巨大的时候同时如果需要使用第三方库的时候,Makefile的管理就会稍显麻烦,除了这一点之外,Makefile还有一个更为致命的问题,其不能很好的对跨平台进行支持,也就是如果需要实现两个不同的平台上使用Makefile那么往往需要编写两份Makefile文件。
为了解决上面Makefile遇到的问题,便有了CMakeCMake能够做什么,这里引用官网的一段描述:

CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice. The suite of CMake tools were created by Kitware in response to the need for a powerful, cross-platform build environment for open-source projects such as ITK and VTK.

上面的英文大致意思就是CMake是一个跨平台的构建工具,用来生成Makefile。这也就是Cmake的主要用途。
CMake现在已经成为很多C++项目使用的构建工具,使用CMake已经成为大势所趋。所以学习CMake就显得尤为重要。

MXNet介绍

MXNet是一个开源的机器学习框架,其支持很多不同的语言,能够方便使用者进行机器学习的训练以及部署。大家熟知的使用Python安装MXNet进行机器学习的训练,而这里的MXNet的代码则是提供一个动态库,对于不同的语言开发者会进行不同的封装,其最终都会调用该动态库。这里MXNet的动态库就是使用C++进行编写的,同时使用CMake进行项目的构建。

MXNet代码组织结构

MXNet的目录下有很多子目录,我们只需要关注其部分目录和文件即可:

src目录:存放源代码和部分头文件
include目录:存放对外发布的库的头文件
test目录:存放测试用例
cmake目录:存放.cmake文件
3rdparty目录:存放第三方库
CMakeList.txt文件:CMake的配置文件

如何使用CMake

使用CMake只需要编写CMakeList.txt文件,在完成文件的编写之后,我们可以通过cmake命令(需要保证执行位置下存在CMakeList.txt如果不存在只需要在命令后面输入文件所在路径),该命令便会为我们生成Makefile文件,在生成Makefile文件之后我们只需要执行make命令即可开始构建项目。

MXNet中的CMakeList.txt

接下来我们来学习MXNet是如何使用CMake进行构建的,在上面的代码组织结构中我们发现该项目除了有自己的源代码以外,还使用了第三方库,而这些第三方实际上也是通过CMake来构建的,对于这样的第三方库CMake能够进行很好的管理。

cmake_minimum_required(VERSION 3.13)

该命令用于指定cmake的最低版本号,当版本号小于3.13cmake将不会继续执行,而是提示用户升级cmake版本。

if() elseif() else() endif()

cmake中的逻辑控制语句,在MXNet中的实例如下:

if(CMAKE_CROSSCOMPILING)
  set(__CMAKE_CROSSCOMPILING ${CMAKE_CROSSCOMPILING})
  set(__CMAKE_CROSSCOMPILING_OVERRIDE ON)
endif()

if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0)
    message(FATAL_ERROR "MXNet 2 requires a C++17 compatible compiler. Please update to GCC version 7 or newer.")
  endif()
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
    message(FATAL_ERROR "MXNet 2 requires a C++17 compatible compiler. Please update to Clang version 6 or newer.")
  endif()
endif()

if语句中第二个参数为判断的条件上面出现的STREQUAL用于判断两个字符串是否相等,VERSION_LESS用于判断前一个版本号是否小于后一个版本号。
上述的第一个if语句会在CMAKE_CROSSCOMPILINGON的时候执行,这里的判断语句应该是用于交叉编译的。

注:CMake中的truefalse是通过ONOFF来表示。

注:交叉编译多用于实现嵌入式设备的代码编译,由于嵌入式设备性能有限,所以一般性能较强的主机上进行编译,将编译后的目标文件(或库)移植到嵌入式设备即可使用。

set()

set命令在CMake中用于定义变量,例如:

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)

如果想要获取某个变量则是通过${VARIABLE_NAME}来实现。
set命令同样可以设置缓冲变量,缓冲变量可以在全局中使用。

cmake的内置变量

一般以CMAKE开头的变量即为cmake的内置变量,可以按照获取变量值的方法获取各个变量的值,通过set命令可以更改这些变量的值,从而达到一些特殊的目的,MXNet中使用到的内置变量:

  • CMAKE_CROSSCOMPILING 跨平台编译的宏,在指定系统名称后该宏会激活
  • CMAKE_CXX_STANDARD 用于指定C++的版本
  • CMAKE_CXX_STANDARD_REQUIRED 当开启的时候一定会使用CMAKE_CXX_STANDARD中指定的版本,如果没有则会停止并报错,关闭时如果没有则使用上一个版本
  • CMAKE_CXX_EXTENSIONS 是否启用编译器扩展的C++
  • CMAKE_CXX_COMPILER_ID 编译器的名称例如GNU
  • CMAKE_CXX_COMPILER_VERSION 编译器的版本号
  • CMAKE_PROJECT_NAME 顶层项目的名称(如果一个项目包含了一个子项目,那么在子项目中CMAKE_ PROJECT_NAME将会是顶层项目的名称,而不是自己项目的名称)
  • PROJECT_NAME 当前项目的名称
  • CMAKE_CURRENT_SOURCE_DIR 当前处理的源目录的绝对路径

project()

该命令用于指令项目的名称以及项目使用的编程语言,MXNet中的例子指定项目名称为mxnet编程语言为C/C++

project(mxnet C CXX)

message()

用于打印提示信息,类似于日志输出可以指定输出的levelMXNet中使用的几种level如下:

message(FATAL_ERROR "MXNet 2 requires a C++17 compatible compiler. Please update to GCC version 7 or newer.")
message(STATUS "CMAKE_CROSSCOMPILING ${CMAKE_CROSSCOMPILING}")
message(WARNING "Could not find NVML libraries")
message(ERROR " Only one of USE_JEMALLOC and USE_GPERFTOOLS can be defined at once")
message("After choosing blas, linking to ${mxnet_LINKER_LIBS}")

注:日志级别决定了会进行的处理,其中FATAL_ERROE会停止cmake的执行。
注:当不输入级别的时候,代表消息是一个重要消息,用户应该关注。
注:cmake中没有ERROR这个级别,但是MXNet中使用了这个级别并且没有出现错误,这是我不太理解的地方。

include()

include可以用于引入宏,模块以及一个文件。

  • 如果引入一个文件(通常后缀为.cmake),此时的用法与C/C++中的include宏用法一致,在mxnet中引入一个文件的用法如下:

    include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Utils.cmake)
    

    上面的命令会引入Utils.cmake该文件中定义了一些常用的工具函数以及宏。\

  • mxnet中还通过include命令引入了模块,例如:

    include(CMakeDependentOption)
    

    上面引入了CmakeDependentOption该模块中主要支持了cmake_dependent_option方法。

cmake_dependent_option()

该方法用于设置一些宏变量,宏变量在依赖成立的情况下设置为一个值,在依赖不满足的情况下设置为另一个值,例如mxnet中的一个例子:

cmake_dependent_option(USE_NVML "Build with nvml support if found" ON "USE_CUDA" OFF)

USE_NVML会在USE_CUDAON的时候为ON在其为OFF的时候为OFF
一个复杂一点的例子:

cmake_dependent_option(A "this is description" ON "B;NOT C" OFF)

上述的代码表示当B打开,C关闭的时候,A会打开;在其余情况A会关闭。

option()

用于设置一个选项,该选项的变量可以在执行cmake命令的时候通过-DOPTION_NAME=ON/OFF来设置其值。

option(USE_NCCL "Use NVidia NCCL with CUDA" OFF)

例如上面设置了一个USE_NCCL的选项,其默认值为OFF中间的字符串为其的说明。

待更新,上次更新2023.6.23

find_package()

execute_process()

string()

add_compile_definitions()

add_definitions()

check_language()

enable_language()

find_program()

猜你喜欢

转载自blog.csdn.net/qq_45523675/article/details/131400932
今日推荐