【征程 6】工具链 VP 示例中 Cmakelists 解读

1. 引言

在文章【征程 6】VP 简介与单算子实操中,介绍了 VP 是什么,并以单算子 rotate 为例,介绍了 VP API 使用方法。在【征程 6】工具链 VP 示例中日志打印解读 中介绍了 VP 单算子示例中用到的日志打印的头文件应该怎么写。接下来和大家一起看一下 Cmakelists 在 VP 示例中扮演的角色。

作为对 C++不那么熟悉的伙伴,可能会好奇:Cmakelists.txt 怎么写?当有多层 Cmakelists.txt 时,他们的调用关系是什么?由于本人是属于对 C++不那么熟悉的同学,下面会从我的视角来介绍这个问题,如果其中有错误或表述不当的地方,欢迎评论指正。

本文是为了服务于另外一篇文章:【征程 6】工具链 VP 示例为什么能运行

2. 基础知识

写一段 c++代码,怎么才能跑起来呢?

简单来说,需要在开发机上编译生成可执行文件,然后将可执行文件(bin)和相关依赖(lib,动静态链接库)拷贝到开发板上,执行"可执行文件"即可。

补充解释一下动静态链接库:

静态(函数)库 扩展名为(。a 或。lib, .a 是 linux 侧,。lib 是 windows 侧),静态库在编译时,会直接整合到目标程序中,编译出来的文件会比较大。

  • 优点:编译出的可执行文件 可以独立运行,不再需要向外部要求读取函数库的内容。
  • 缺点:从升级难易度来看没有优势,如果函数库更新,需要重新编译。

动态(函数)库 扩展名为(。so 或。dll, .so 是 linux 侧,。dll 是 windows 侧),动态库在编译时,在程序里只有一个"指向"的位置,也就是说,当可执行文件需要使用到 函数库 中的内容时,程序才会去读取 函数库 来使用。

  • 优点:方便产品功能升级,只要替换对应动态库即可,不必重新编译整个可执行文件。
  • 缺点:可执行文件无法单独运行。

在构建稍微规范/复杂点的 C++项目时,通常会用到 CmakeList,下面来看一下。

2.1 代码结构

构建一个简单但典型的 case 来说明一个 C++项目的调用运行逻辑,项目结构如下:

Plain 复制代码
./
│── CMakeLists.txt       # 顶层 CMakeLists.txt
│── src/                 # 源代码目录
│   ├── CMakeLists.txt   # 子目录 CMakeLists.txt
│   ├── main.cpp         # 主程序
│   ├── main.h           # 头文件
│   ├── module/          # 子模块,如果有的话
│   │   ├── CMakeLists.txt
│   │   ├── module.cpp
│   │   ├── module.h
│── build/               # CMake 编译输出目录,在这个目录里进行编译

在 CMake 项目中,通常会有 顶层 CMakeLists.txt,以及 src 子目录中的 嵌套 CMakeLists.txt,它们共同组织代码的编译流程。CMake 使用 层次化调用,即 顶层 CMakeLists.txt 调用子目录 CMakeLists.txt。编译过程如下:

顶层 CMakeLists.txt: 设置编译选项、添加 src 目录、指定最终目标

src/CMakeLists.txt: 添加 main.cpp 和 main.h、包含子模块

module/CMakeLists.txt: 定义 module.cpp、生成库文件

2.2 CmakeList

  • 最外层 CmakeLists.txt
Plain 复制代码
cmake_minimum_required(VERSION 3.10)
project(MyProject)    # 定义项目名称

set(CMAKE_CXX_STANDARD 11)   # 使用 C++11 标准
# 添加子目录 (调用 src/CMakeLists.txt)
# 让 CMake 进入 src 目录,继续解析 src/CMakeLists.txt
add_subdirectory(src)
# 最终的可执行目标由 src 目录下的 CMakeLists.txt 负责
  • src/CMakeLists.txt
Plain 复制代码
cmake_minimum_required(VERSION 3.10)
# 添加子模块 (module 目录), 进入 module 目录,调用 module/CMakeLists.txt
add_subdirectory(module)
# 让编译器找到 module.h
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/module)
# 指定 main.cpp 作为源文件
set(SRC_FILES main.cpp)
# 生成MyExecutable可执行文件
add_executable(MyExecutable ${SRC_FILES})
# 链接 MyModule库, MyModule在module/CMakeLists.txt中定义
target_link_libraries(MyExecutable MyModule)
  • src/module/CMakeLists.txt
Plain 复制代码
cmake_minimum_required(VERSION 3.10)
# 包含当前目录头文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# 编译 module.cpp 为 MyModule 静态库
add_library(MyModule STATIC module.cpp)

2.3 Main

  • main.cpp
Plain 复制代码
#include <iostream>
#include "main.h"
#include "module.h"

int main() {
    std::cout << "Hello, main c++ CMake!" << std::endl;
    module_function();
    return 0;
}
  • main.h
Plain 复制代码
#ifndef MAIN_H
#define MAIN_H

#endif // MAIN_H

2.4 module

  • module.cpp
Plain 复制代码
#include "module.h"
#include <iostream>

void module_function() {
    std::cout << "Module function called!" << std::endl;
}
  • module.h
Plain 复制代码
#ifndef MODULE_H
#define MODULE_H

void module_function();

#endif // MODULE_H

2.5 编译与执行

Plain 复制代码
# 进入项目目录
cd ./

# 删除旧的 build 目录,重新编译
rm -rf build
mkdir build && cd build

# 运行 CMake 配置
cmake ..

# 编译
make -j8

# 运行可执行文件
./src/MyExecutable

输出:

Plain 复制代码
Hello, main c++ CMake!
Module function called!

3. VP 示例中 Cmakelists

3.1 vp/code/07_single_rotate/CMakelists.txt

在 vp/code/07_single_rotate 目录下的 CMakelists.txt,解读如下:

Plain 复制代码
# 设置 CMake 最低版本,确保兼容性
cmake_minimum_required(VERSION 3.0)
# 定义项目名称为 07_single_rotate
project(07_single_rotate)
# 设置编译选项,添加 -std=c++11 选项,使用 C++11 标准进行编译
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ")

# include_directories(...) 指定 头文件搜索路径,用于 编译时查找头文件
include_directories(     
        ${CMAKE_CURRENT_SOURCE_DIR}/        # 当前源码目录
        ${DEPS_SOURCE_DIR}/ucp/include/     # UCP 组件头文件   
        ${DEPS_SOURCE_DIR}/hlog/include/    # hlog 日志库
        ${DEPS_SOURCE_DIR}/fmt/include/     # fmt 格式化库
        ${DEPS_SOURCE_DIR}/opencv/include/) # OpenCV 视觉库

# 指定库文件 (.so 或 .a) 搜索路径,用于 链接时查找库文件
link_directories(${DEPS_SOURCE_DIR}/ucp/lib
        ${DEPS_SOURCE_DIR}/opencv/lib
        ${DEPS_SOURCE_DIR}/hlog/lib
        ${DEPS_SOURCE_DIR}/fmt/lib
        ${DEPS_SOURCE_DIR}/opencv/lib)        
# 递归搜索 *.cpp 源文件,并存储到 TEST_SRC 变量。
# GLOB_RECURSE 会搜索 当前目录及子目录 的 .cpp 文件。
file(GLOB_RECURSE TEST_SRC *.cpp)
# 创建可执行文件 single_rotate,包含 TEST_SRC 变量中的所有 .cpp 文件
add_executable(single_rotate ${TEST_SRC})
# target_link_libraries(...) 链接所需的库
target_link_libraries(single_rotate hbucp hbvp hlog fmt opencv_world)
# 将 single_rotate 安装到 RELEASE_BIN_DIR 目录。
install(TARGETS single_rotate DESTINATION ${RELEASE_BIN_DIR})
  1. DEPS_SOURCE_DIR 在哪设置的?(3.2 节中设置)
  2. RELEASE_BIN_DIR 在哪设置的?(3.2 节中设置)

带着这 2 个疑问,我们往外看一层 CmakeLists.txt。

3.2 vp/code/CMakelists.txt

Plain 复制代码
# 设置 CMake 最低版本,确保兼容性
cmake_minimum_required(VERSION 3.0)
# 项目名称 设置为 vp_samples
project(vp_samples)
# 定义 编译选项 PLATFORM_AARCH64,默认 开启 (ON),用于 区分 aarch64(ARM 架构)和 x86(PC 端)
option(PLATFORM_AARCH64 "Target platform aarch64" ON)
# 强制使用 C++11 标准 进行编译
set(CMAKE_CXX_STANDARD 11)
# 设置编译模式,变量 CMAKE_BUILD_TYPE 由外部传入,通常可选Debug/Release
SET(CMAKE_BUILD_TYPE ${build_type})
# message(...) 打印当前编译模式
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

# 设置编译 & 预处理选项,几个选项解读见下面
set(CMAKE_CXX_FLAGS "-std=c++11 -Wno-unknown-pragmas -fPIC -O3 -Wl,-unresolved-symbols=ignore-in-shared-libs")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb ")
set(CMAKE_C_FLAGS "-Wno-unknown-pragmas -fPIC -O3")
# 打印编译参数
message("CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
message("CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}")

# 根据平台 (aarch64 or x86) 设置依赖路径
if(PLATFORM_AARCH64)
    message(STATUS "build aarch64")
    # 依赖路径 DEPS_SOURCE_DIR 设为 ...deps_aarch64
    set(DEPS_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../../deps_aarch64)
else()
    # 添加编译宏 -DUCP_X86
    add_definitions(-DUCP_X86)
    message(STATUS "build x86")
    # 依赖路径 DEPS_SOURCE_DIR 设为 ...deps_x86
    set(DEPS_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../../deps_x86)
endif()

if(PLATFORM_AARCH64)
    # 设置输出路径
    set(OUTPUT_ROOT ${PROJECT_SOURCE_DIR}/../vp_samples/script/)
    set(RELEASE_LIB_DIR ${OUTPUT_ROOT}/aarch64/lib/)
    set(RELEASE_BIN_DIR ${OUTPUT_ROOT}/aarch64/bin/)
    # 复制 文件
    install(DIRECTORY ${DEPS_SOURCE_DIR}/ucp/bin/image DESTINATION ${RELEASE_BIN_DIR})
else()
    set(OUTPUT_ROOT ${PROJECT_SOURCE_DIR}/../vp_samples/script_x86/)
    set(RELEASE_LIB_DIR ${OUTPUT_ROOT}/x86/lib/)
    set(RELEASE_BIN_DIR ${OUTPUT_ROOT}/x86/bin/)
    # 复制 文件
    file(COPY ${DEPS_SOURCE_DIR}/ucp/bin DESTINATION ${OUTPUT_ROOT}/x86/)
endif()

# 安装文件(通常是库文件、头文件等)到目标目录
install(FILES ${DEPS_SOURCE_DIR}/opencv/lib/libopencv_world.so.3.4 DESTINATION ${RELEASE_LIB_DIR})
# file(GLOB HLOG_LIBS "...") 匹配 libhlog.so* 并安装
file(GLOB HLOG_LIBS "${DEPS_SOURCE_DIR}/hlog/lib/libhlog.so*")
install(FILES ${HLOG_LIBS} DESTINATION ${RELEASE_LIB_DIR})
install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libhbucp.so DESTINATION ${RELEASE_LIB_DIR})
install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libhlog_wrapper.so DESTINATION ${RELEASE_LIB_DIR})
install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libhbvp.so DESTINATION ${RELEASE_LIB_DIR})
install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libhb_arm_rpc.so DESTINATION ${RELEASE_LIB_DIR})
install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libperfetto_sdk.so DESTINATION ${RELEASE_LIB_DIR})
# 额外安装 x86 平台的特殊库
if(NOT PLATFORM_AARCH64)
    install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libarm_model_gdc.so DESTINATION ${RELEASE_LIB_DIR})
    install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libhbmem.so.1 DESTINATION ${RELEASE_LIB_DIR})
    install(FILES ${DEPS_SOURCE_DIR}/ucp/lib/libalog.so.1 DESTINATION ${RELEASE_LIB_DIR})
endif()
# 添加子项目 07_single_rotate
add_subdirectory(07_single_rotate)
  1. PROJECT_SOURCE_DIR 在哪设置的? 答:PROJECT_SOURCE_DIR 是指向项目源代码根目录的 CMake 预定义变量,常用于构建路径、引用资源文件等,帮助 CMake 找到项目中的文件。
  2. build_type 在哪设置的? 答:在 build.sh 脚本中,详情可见文档:【征程 6】工具链 VP 示例为什么能运行

4. 总结

本文主要介绍在 VP 单算子示例中用到的 CmakeLists.txt 应该怎么写,主要适用于和我一样 C++基础学习用户~

相关推荐
HERR_QQ1 小时前
强化学习的数学原理 学习笔记
人工智能·笔记·学习·自动驾驶
kebidaixu2 小时前
两轮BMS 防打火策略详解
算法
wabs6662 小时前
关于动态规划【力扣1035.不相交的线和53.最大子数组和的思考】
算法·leetcode·动态规划
退休倒计时2 小时前
【每日一题】LeetCode 199. 二叉树的右视图 TypeScript
算法·leetcode·typescript
可编程芯片开发2 小时前
通过MATLAB实现PID控制器,积分分离控制器以及滑模控制器
算法
kebidaixu3 小时前
两轮BMS 短路保护策略详解
算法
zwenqiyu3 小时前
非线性字符串数据结构串讲
数据结构·c++·学习·算法
z小猫不吃鱼3 小时前
03 Optimal Brain Surgeon 详解:Hessian 剪枝为什么有效?
算法·机器学习·剪枝
硕风和炜3 小时前
【LeetCode: 1301. 最大得分的路径数目 + DP】
java·算法·leetcode·动态规划·dp·记忆化搜索
用户99045017780094 小时前
做了一个AI诊断,参考倪海厦中医理论,科学养生
算法