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

发布于:2025-04-13 ⋅ 阅读:(16) ⋅ 点赞:(0)

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++项目的调用运行逻辑,项目结构如下:

./
│── 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
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
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
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
#include <iostream>
#include "main.h"
#include "module.h"

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

#endif // MAIN_H

2.4 module

  • module.cpp
#include "module.h"
#include <iostream>

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

void module_function();

#endif // MODULE_H

2.5 编译与执行

# 进入项目目录
cd ./

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

# 运行 CMake 配置
cmake ..

# 编译
make -j8

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

输出:

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,解读如下:

# 设置 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

# 设置 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++基础学习用户~


网站公告

今日签到

点亮在社区的每一天
去签到