C/C++编译工具:cmake | AI工程化部署

简介: CMake 是一个跨平台的开源构建工具,用于管理软件构建流程。它使用一个名为 CMakeLists.txt 的文本文件来描述构建过程。【1月更文挑战第4天】

1.基本使用

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 文件生成相应的构建系统文件,并且帮助我们完成项目的构建过程。

2.基本语法

  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 build
cd build
cmake ..
make

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

3.指定安装目录

我们进行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时,可以根据具体的项目需求来设置不同的安装路径和安装目标。

4.区分开发版与发布版

在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 ..

5.大杀器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>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
PATH

其中,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 ..

6.多个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.txt
add_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文件,并将子目录中的源文件添加到主项目中。

7.打印信息

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})
目录
相关文章
|
1月前
|
人工智能 并行计算 安全
从零到一,打造专属AI王国!大模型私有化部署全攻略,手把手教你搭建、优化与安全设置
【10月更文挑战第24天】本文详细介绍从零开始的大模型私有化部署流程,涵盖需求分析、环境搭建、模型准备、模型部署、性能优化和安全设置六个关键步骤,并提供相应的示例代码,确保企业能够高效、安全地将大型AI模型部署在本地或私有云上。
594 7
|
18天前
|
人工智能 Java Serverless
阿里云函数计算助力AI大模型快速部署
随着人工智能技术的快速发展,AI大模型已经成为企业数字化转型的重要工具。然而,对于许多业务人员、开发者以及企业来说,探索和利用AI大模型仍然面临诸多挑战。业务人员可能缺乏编程技能,难以快速上手AI模型;开发者可能受限于GPU资源,无法高效构建和部署AI应用;企业则希望简化技术门槛,以更低的成本和更高的效率利用AI大模型。
87 12
|
4天前
|
人工智能 数据库连接 API
在部署《主动式智能导购 AI 助手构建》解决方案的过程中,整体体验还是相对顺畅的,但确实遇到了一些问题,文档提供的引导也有所不足,以下是详细的体验评估
在部署《主动式智能导购 AI 助手构建》解决方案的过程中,整体体验还是相对顺畅的,但确实遇到了一些问题,文档提供的引导也有所不足,以下是详细的体验评估
|
16天前
|
人工智能 缓存 异构计算
云原生AI加速生成式人工智能应用的部署构建
本文探讨了云原生技术背景下,尤其是Kubernetes和容器技术的发展,对模型推理服务带来的挑战与优化策略。文中详细介绍了Knative的弹性扩展机制,包括HPA和CronHPA,以及针对传统弹性扩展“滞后”问题提出的AHPA(高级弹性预测)。此外,文章重点介绍了Fluid项目,它通过分布式缓存优化了模型加载的I/O操作,显著缩短了推理服务的冷启动时间,特别是在处理大规模并发请求时表现出色。通过实际案例,展示了Fluid在vLLM和Qwen模型推理中的应用效果,证明了其在提高模型推理效率和响应速度方面的优势。
云原生AI加速生成式人工智能应用的部署构建
|
19天前
|
机器学习/深度学习 存储 人工智能
【AI系统】训练后量化与部署
本文详细介绍了训练后量化技术,涵盖动态和静态量化方法,旨在将模型权重和激活从浮点数转换为整数,以优化模型大小和推理速度。通过KL散度等校准方法和量化粒度控制,文章探讨了如何平衡模型精度与性能,同时提供了端侧量化推理部署的具体实现步骤和技术技巧。
43 1
【AI系统】训练后量化与部署
|
24天前
|
人工智能 监控 Serverless
《主动式智能导购AI助手构建》解决方案部署测评
在数字化时代,智能导购AI助手已成为提升客户体验和销售效率的重要工具。本文将基于个人体验,对《主动式智能导购AI助手构建》解决方案的部署过程进行详细评测。
40 3
|
10天前
|
人工智能 API Windows
免费部署本地AI大语言模型聊天系统:Chatbox AI + 马斯克grok2.0大模型(简单5步实现,免费且比GPT4.0更好用)
本文介绍了如何部署本地AI大语言模型聊天系统,使用Chatbox AI客户端应用和Grok-beta大模型。通过获取API密钥、下载并安装Chatbox AI、配置模型,最终实现高效、智能的聊天体验。Grok 2大模型由马斯克X-AI发布,支持超长文本上下文理解,免费且易于使用。
44 0
|
1月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
104 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
92 4