写点什么

cmake | AI 工程化部署

作者:AIWeker
  • 2023-12-24
    福建
  • 本文字数:5288 字

    阅读完需:约 17 分钟

C/C++编译工具:cmake

基本使用

CMake 是一个跨平台的开源构建工具,用于管理软件构建流程。它使用一个名为 CMakeLists.txt 的文本文件来描述构建过程。以下是一个简单的 CMakeLists.txt 文件的示例,用于构建一个简单的 C++ 程序:


# 指定 CMake 最低版本要求cmake_minimum_required(VERSION 3.10)
# 指定项目名称project(MyProject)
# 指定生成可执行文件add_executable(my_program main.cpp)
复制代码


在这个示例中,我们指定了 CMake 的最低版本要求,并且指定了项目的名称。然后,我们使用 add_executable 命令来告诉 CMake 创建一个名为 my_program 的可执行文件,该可执行文件由 main.cpp 文件构建而成。


为了使用 CMake 构建项目,我们需要执行以下步骤:


  1. 创建一个 CMakeLists.txt 文件来描述构建过程。

  2. 在项目根目录下创建一个 build 文件夹,并进入该文件夹。

  3. 运行 cmake 命令来生成构建系统所需的文件。例如:cmake ..

  4. 运行生成的构建系统,例如使用 make 命令进行构建。


通过这些步骤,CMake 将会根据 CMakeLists.txt 文件生成相应的构建系统文件,并且帮助我们完成项目的构建过程。

基本语法

  command(arg1 arg2 ...) # 运行命令  set(var_name var_value) # 定义变量,或者给已经存在的变量赋值  command(arg1 ${var_name}) # 使用变量
# 控制语句 IF(expression) COMMAND1(ARGS) ELSE(expression) COMMAND2(ARGS) ENDIF(expression)
# expression IF(var) # 不是空, 0, N, NO, OFF, FALSE, NOTFOUND 或 _NOTFOUND时,为真 IF(NOT var) # 与上述条件相反。 IF(var1 AND var2) # 当两个变量都为真是为真。 IF(var1 OR var2) # 当两个变量其中一个为真时为真。 IF(COMMAND cmd) # 当给定的cmd确实是命令并可以调用是为真 IF(EXISTS dir) # 目录名存在 IF(EXISTS file) # 文件名存在 IF(IS_DIRECTORY dirname) # 当dirname是目录 IF(file1 IS_NEWER_THAN file2) # 当file1比file2新,为真 IF(variable MATCHES regex) # 符合正则
# 循环 WHILE(condition) COMMAND1(ARGS) // ... ENDWHILE(condition)
AUX_SOURCE_DIRECTORY(. SRC_LIST) FOREACH(one_dir ${SRC_LIST}) MESSAGE(${one_dir}) ENDFOREACH(onedir)
复制代码

基本操作

  • add_library: 指定的源文件(CPP 文件)生成链接文件,然后添加到工程中去。生成动态库或者静态库


    add_library(<name> [STATIC | SHARED | MODULE]            [EXCLUDE_FROM_ALL]            [source1] [source2 ...])
add_library(mod1 SHARED mod1.c mod1_func.c) # 生成动态库 libmod1.so add_library(mod2 STATIC mod2.c) # 生成静态库 libmod2.a
复制代码


  • add_subdirectory: 在子文件夹添加了 library 或者 executable 之后,在上层目录添加 subdirectory, 也可以在同一个 CMakeList.txt 中使用

  • target_link_libraries: 设置要链接的库文件的名称 具体的动态链接库文件.so, 在生成的可执行文件或动态库中连接进去其他的库


    target_link_libraries(<target> [item1 [item2 [...]]]                        [[debug|optimized|general] <item>] ...)    target_link_libraries(main mod1) 
复制代码


  • include_directories: 添加头文件目录 g++选项中的-I 参数的作用,也相当于环境变量中增加路径到 CPLUS_INCLUDE_PATH 变量的作用

  • link_directories 添加需要链接的库文件目录。 它相当于 g++命令的-L 选项的作用,也相当于环境变量中增加 LD_LIBRARY_PATH 的路径的作用

  • link_libraries 添加需要链接的库文件路径


下面是一个使用 CMake 构建的示例,其中包括了 target_link_libraries,link_directories,include_directories,add_executable 和 add_library 等关键部分,以生成一个可执行文件和一个动态库。


cmake_minimum_required(VERSION 3.5)
project(MyProject)
# 设置编译选项set(CMAKE_CXX_STANDARD 11)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Ofast -Wfatal-errors -pthread -fPIC -O3 -mavx")
# 添加动态库的源文件set(SOURCE_FILES_LIB mylib.cpp)add_library(mylib SHARED ${SOURCE_FILES_LIB})
# 添加可执行文件的源文件set(SOURCE_FILES main.cpp)
# 添加包含目录include_directories(include)
# 添加链接目录link_directories(${CMAKE_SOURCE_DIR}/lib)
# 添加可执行文件add_executable(myapp ${SOURCE_FILES})
# 链接动态库target_link_libraries(myapp mylib)
复制代码


假设项目的目录结构如下所示:


MyProject/├── CMakeLists.txt├── include/│   └── mylib.h├── src/│   ├── main.cpp│   └── mylib.cpp└── lib/    └── libmylib.so
复制代码


在项目根目录中执行以下命令来生成构建文件和编译项目:


mkdir buildcd buildcmake ..make
复制代码


这将生成一个名为 myapp 的可执行文件和一个名为 libmylib.so 的动态库。

指定安装目录

我们进行 cmake 命令,会执行 make 命令进行编译,最后如果有需要的话会执行 make install 将编译好的库或者头文件安装到指定的未知。


在 CMake 中,可以使用install命令来设置安装相关信息,包括安装目标、安装路径等。以下是一个详细的例子,展示了如何在 CMake 中设置安装相关信息:


cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 添加可执行文件add_executable(MyExecutable main.cpp)
# 设置安装路径set(CMAKE_INSTALL_PREFIX /usr/local) # 安装到/usr/local目录下
# 安装可执行文件install(TARGETS MyExecutable DESTINATION bin) # 将可执行文件安装到bin目录下
# 安装头文件file(GLOB HEADER_FILES "*.h")install(FILES ${HEADER_FILES} DESTINATION include/myproject) # 将头文件安装到include/myproject目录下
# 安装额外文件install(FILES README.md DESTINATION share/myproject) # 将README.md文件安装到share/myproject目录下
复制代码


在上面的例子中,我们首先使用add_executable命令添加了一个可执行文件MyExecutable。然后使用set命令设置了安装路径为/usr/local。接下来使用install命令分别安装了可执行文件、头文件和额外文件到指定的安装路径下。


需要注意的是,安装路径可以根据实际需要进行调整。在使用 CMake 时,可以根据具体的项目需求来设置不同的安装路径和安装目标。

区分开发版与发布版

在 CMake 中,可以通过MAKE_BUILD_TYPE在确定不通类型的棒棒,常用的构建类型包括 Debug、Release、MinSizeRel 和 RelWithDebInfo。


举例来说,假设我们有一个 CMakeLists.txt 文件,其中需要根据不同的构建类型来设置编译选项。我们可以使用MAKE_BUILD_TYPE来指定构建类型,然后根据不同的构建类型来设置不同的编译选项。


cmake_minimum_required(VERSION 3.10)
project(MyProject)
# 根据构建类型设置编译选项if (MAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DDEBUG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")elseif (MAKE_BUILD_TYPE STREQUAL "Release") add_definitions(-DNDEBUG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")endif()
# 添加源文件add_executable(MyExecutable main.cpp)
复制代码


在上面的例子中,根据不同的构建类型,我们设置了不同的预处理宏和编译选项。当构建类型为 Debug 时,会定义DEBUG宏并启用调试信息;当构建类型为 Release 时,会定义NDEBUG宏并开启优化。


这样我们就可以 main.cpp 中,通过条件编译的方式,确定是哪些 debug 模型的需要编译的,而在 release 发布版本不需要编译,形如:


#ifndef NDEBUG     printf("author: %s, release_date: %s\n", AUTHOR, RELEASE_DATE ); // 只在开发版本编译#endif
复制代码


cmake -DMAKE_BUILD_TYPE=Debug ..cmake -DMAKE_BUILD_TYPE=Release ..
复制代码

大杀器 find_package

find_package 是 CMake 中用于查找和加载第三方库的命令。它用于在系统中查找指定的软件包,并将其路径或库的相关信息导入到 CMake 中,以便在项目中使用。


要配置 find_package,你需要在 CMakeLists.txt 文件中使用 find_package 命令,并提供要查找的软件包的名称。通常情况下,你还需要指定软件包的版本号。


下面是一个使用 find_package 的简单例子:


假设你想在 CMake 项目中使用 OpenCV,你需要在 CMakeLists.txt 文件中添加以下内容:


# 查找OpenCV包find_package(OpenCV 4.0 REQUIRED)
# 如果找到OpenCV包,将其包含路径和链接库添加到项目中if(OpenCV_FOUND) include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(your_project_name ${OpenCV_LIBS})endif()
复制代码


find_package 语法为:FIND_PACKAGE( <name> [version] [EXACT] [QUIET] [NO_MODULE] [ [ REQUIRED | COMPONENTS ] [ componets... ] ] )


在这个例子中,find_package 命令用于查找 OpenCV 4.0,并将其导入到项目中。如果找到了 OpenCV 包,将其包含路径添加到项目的 include 路径中,并将其链接库添加到项目的链接库中。


请注意,在实际项目中,你可能还需要根据你的项目结构和依赖项的不同做一些适当的调整。


那 find_package 怎么知道从哪里去查找相关依赖库呢?查找的目录路径:


<package>_DIRCMAKE_PREFIX_PATHCMAKE_FRAMEWORK_PATHCMAKE_APPBUNDLE_PATHPATH
其中,PATH中的路径如果以bin或sbin结尾,则自动回退到上一级目录。找到根目录后,cmake会检查这些目录下的
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U)<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)cmake找到这些目录后,会开始依次找<package>Config.cmake或Find<package>.cmake文件。找到后即可执行该文件并生成相关链接信息。
最重要的一个是PATH。由于/usr/bin/在PATH中,cmake会自动去/usr/(lib/<arch>|lib|share)/cmake/<name>*/寻找模块另外一个比较重要的是<package>_DIR。我们可以在调用cmake时将这个目录传给cmake。由于其优先级最高,因此cmake会优先从该目录中寻找,这样我们就可以随心所欲的配置cmake使其找到我们希望它要找到的包。而且除上述指定路径外,cmake还会直接进入<package>_DIR下寻找。如我在3rd_parties目录下编译了一个OpenCV,那么执行cmake时可以使用
OpenCV_DIR=../../3rd-party/opencv-3.3.4/build/ cmake ..
复制代码

多个 CMakeLists.txt

在一个复杂的项目中,通常会以模块化的方式来组织项目的框架,统一一个如 main.cpp 做为入口程序(如 ffmpeg)。那么如果所有的程序的编译信息都写在一个 CMakeLists.txt 会很难维护。cmake 里运行多个 CMakeLists.txt,每个模块有自己的 CMakeList 负责编译。如下面的结构


  .  ├── build  │   ├── CMakeCache.txt  │   ├── CMakeFiles  │   ├── cmake_install.cmake  │   ├── lib  │   │   ├── libmod1.so  │   │   └── mo2_lib  │   │       ├── libmod2.a  │   │       └── Makefile  │   └── Makefile  ├── CMakeLists.txt  ├── main.c  └── mod1      ├── CMakeLists.txt      ├── mod1.c      ├── mod1_func.c      ├── mod1_func.h      ├── mod1.h      └── mod2          ├── CMakeLists.txt          ├── mod2.c          └── mod2.h
复制代码


具体的,当使用 CMake 构建一个项目时,add_subdirectory命令可以用来包含子目录中的 CMakeLists.txt 文件,从而将子目录中的源代码文件添加到主项目中。下面是一个使用add_subdirectory的简单示例:


假设我们有以下项目目录结构:


project/    CMakeLists.txt    main.cpp    subdirectory/        CMakeLists.txt        helper.cpp        helper.h
复制代码


在主项目的 CMakeLists.txt 文件中,可以包含子目录的 CMakeLists.txt 文件使用 add_subdirectory 命令。示例如下:


# 主项目的 CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(MyProject)
add_executable(my_app main.cpp)
# 包含subdirectory中的CMakeLists.txtadd_subdirectory(subdirectory)
# 将子目录中的源文件添加到主项目中target_sources(my_app PRIVATE subdirectory/helper.cpp)
复制代码


在子目录的 CMakeLists.txt 文件中,可以定义子目录中的源文件,示例如下:


# 子目录的 CMakeLists.txt
# 将子目录中的源文件添加到一个库中add_library(helper_lib helper.cpp helper.h)
复制代码


在这个例子中,add_subdirectory命令被用来引入子目录中的 CMakeLists.txt 文件,并将子目录中的源文件添加到主项目中。

打印信息

cmake 可以通过 message 来打印一些调试信息


    cmake_minimum_required(VERSION 2.8)    project(find_package_learning)    find_package(OpenCV 3 REQUIRED)
message(STATUS "OpenCV_DIR = ${OpenCV_DIR}") message(STATUS "OpenCV_INCLUDE_DIRS = ${OpenCV_INCLUDE_DIRS}") message(STATUS "OpenCV_LIBS = ${OpenCV_LIBS}")
include_directories(${OPENCV_INCLUDE_DIRS}) add_executable(opencv_test opencv_test.cpp) target_link_libraries(opencv_test ${OpenCV_LIBS})
复制代码


发布于: 刚刚阅读数: 4
用户头像

AIWeker

关注

InfoQ签约作者 / 公众号:人工智能微客 2019-11-21 加入

人工智能微客(aiweker)长期跟踪和分享人工智能前沿技术、应用、领域知识,不定期的发布相关产品和应用,欢迎关注和转发

评论

发布
暂无评论
cmake | AI工程化部署_c_AIWeker_InfoQ写作社区