Journey 6|Understanding CMakeLists in the VP Toolchain Example

Journey 6|Understanding CMakeLists in the VP Toolchain Example01 Introduction

In the article Journey 6|Introduction to VP and Practical Use of Single Operators we introduced what VP is and demonstrated how to use the VP API with the single operator rotate as an example. In Journey 6|Understanding Log Printing in the VP Toolchain Example we discussed how to write the header files for log printing used in the VP single operator example. Next, let’s take a look at the role of CMakeLists in the VP example.

For those who are not very familiar with C++, you might wonder: how to write CMakeLists.txt? When there are multiple layers of CMakeLists.txt, what are their calling relationships? Since I am also someone who is not very familiar with C++, I will introduce this issue from my perspective. If there are any errors or inappropriate expressions, please feel free to comment and correct me.

This article serves as a companion to another article:Journey 6|Why the VP Toolchain Example Can Run.02 Basic Knowledge

How can a piece of C++ code run?

In simple terms, you need to compile it into an executable file on the development machine, then copy the executable file (bin) and related dependencies (lib, dynamic and static libraries) to the development board, and execute the “executable file”.

Let’s clarify dynamic and static libraries:

A static (function) library has the extension (.a or .lib, .a is for Linux, .lib is for Windows). A static library is directly integrated into the target program during compilation, resulting in a larger compiled file.

  • Advantages: The compiled executable file can run independently without needing to read the function library from external sources.
  • Disadvantages: There is no advantage in terms of upgrade difficulty; if the function library is updated, recompilation is required.

A dynamic (function) library has the extension (.so or .dll, .so is for Linux, .dll is for Windows). A dynamic library only has a “pointer” in the program during compilation, meaning that when the executable file needs to use content from the function library, the program will read from the function library.

  • Advantages: It is convenient for product function upgrades; you only need to replace the corresponding dynamic library without recompiling the entire executable file.
  • Disadvantages: The executable file cannot run independently.

When building a slightly more standardized/complex C++ project, CMakeLists is usually used. Let’s take a look.

2.1 Code Structure

To illustrate the calling and running logic of a C++ project, we will build a simple but typical case with the following project structure:

./│── CMakeLists.txt       # Top-level CMakeLists.txt│── src/                 # Source code directory│   ├── CMakeLists.txt   # Subdirectory CMakeLists.txt│   ├── main.cpp         # Main program│   ├── main.h           # Header file│   ├── module/          # Submodule, if any│   │   ├── CMakeLists.txt│   │   ├── module.cpp│   │   ├── module.h│── build/               # CMake compilation output directory, compile in this directory

In a CMake project, there is usually a top-level CMakeLists.txt and nested CMakeLists.txt in the src subdirectory, which together organize the compilation process of the code. CMake uses hierarchical calls, meaning the top-level CMakeLists.txt calls the subdirectory CMakeLists.txt. The compilation process is as follows:

  • Top-level CMakeLists.txt: Sets compilation options, adds the src directory, and specifies the final target;
  • src/CMakeLists.txt: Adds main.cpp and main.h, includes submodules;
  • module/CMakeLists.txt: Defines module.cpp and generates the library file.

2.2 CMakeLists

  • Outer CMakeLists.txt
cmake_minimum_required(VERSION 3.10)project(MyProject)    # Define project name
set(CMAKE_CXX_STANDARD 11)   # Use C++11 standard
# Add subdirectory (call src/CMakeLists.txt)
# Let CMake enter the src directory and continue parsing src/CMakeLists.txt
add_subdirectory(src)
# The final executable target is managed by the CMakeLists.txt in the src directory
  • src/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
# Add submodule (module directory), enter the module directory, call module/CMakeLists.txt
add_subdirectory(module)
# Let the compiler find module.h
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/module)
# Specify main.cpp as the source file
set(SRC_FILES main.cpp)
# Generate MyExecutable executable file
add_executable(MyExecutable ${SRC_FILES})
# Link MyModule library, MyModule is defined in module/CMakeLists.txt
target_link_libraries(MyExecutable MyModule)
  • src/module/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
# Include header files from the current directory
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# Compile module.cpp into MyModule static library
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 Compilation and Execution

# Enter project directory
cd ./
# Delete old build directory and recompile
rm -rf build
mkdir build && cd build
# Run CMake configuration
cmake ..
# Compile
make -j8
# Run executable file
./src/MyExecutable

Output:

Hello, main c++ CMake! Module function called!

03 CMakeLists in the VP Example3.1 vp/code/07_single_rotate/CMakeLists.txt

The CMakeLists.txt in the vp/code/07_single_rotate directory is interpreted as follows:

# Set minimum CMake version to ensure compatibility
cmake_minimum_required(VERSION 3.0)
# Define project name as 07_single_rotate
project(07_single_rotate)
# Set compilation options, add -std=c++11 option to compile with C++11 standard
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ")
# include_directories(...) specifies header file search paths for compiling
include_directories(             ${CMAKE_CURRENT_SOURCE_DIR}/        # Current source directory        ${DEPS_SOURCE_DIR}/ucp/include/     # UCP component header files           ${DEPS_SOURCE_DIR}/hlog/include/    # hlog logging library        ${DEPS_SOURCE_DIR}/fmt/include/     # fmt formatting library        ${DEPS_SOURCE_DIR}/opencv/include/) # OpenCV vision library
# Specify library file (.so or .a) search paths for linking
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)        # Recursively search for *.cpp source files and store them in TEST_SRC variable.
# GLOB_RECURSE will search for .cpp files in the current directory and subdirectories.
file(GLOB_RECURSE TEST_SRC *.cpp)
# Create executable file single_rotate, including all .cpp files in TEST_SRC variable
add_executable(single_rotate ${TEST_SRC})
# target_link_libraries(...) links the required libraries
target_link_libraries(single_rotate hbucp hbvp hlog fmt opencv_world)
# Install single_rotate to RELEASE_BIN_DIR directory.
install(TARGETS single_rotate DESTINATION ${RELEASE_BIN_DIR})
  1. Where is DEPS_SOURCE_DIR set? (See section 3.2)
  2. Where is RELEASE_BIN_DIR set? (See section 3.2)

With these two questions, let’s look one layer out at the CMakeLists.txt.3.2 vp/code/CMakeLists.txt

# Set minimum CMake version to ensure compatibility
cmake_minimum_required(VERSION 3.0)
# Set project name to vp_samples
project(vp_samples)
# Define compilation option PLATFORM_AARCH64, default ON, used to distinguish between aarch64 (ARM architecture) and x86 (PC)
option(PLATFORM_AARCH64 "Target platform aarch64" ON)
# Force use of C++11 standard for compilation
set(CMAKE_CXX_STANDARD 11)
# Set compilation mode, variable CMAKE_BUILD_TYPE is passed from external, usually options are Debug/Release
SET(CMAKE_BUILD_TYPE ${build_type})
# message(...) prints the current compilation mode
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
# Set compilation & preprocessing options, several options are explained below
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")
# Print compilation parameters
message("CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
message("CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}")
# Set dependency paths based on platform (aarch64 or x86)
if(PLATFORM_AARCH64)    
    message(STATUS "build aarch64")    
    # Set DEPS_SOURCE_DIR to ...deps_aarch64
    set(DEPS_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../../deps_aarch64)
else()    
    # Add compilation macro -DUCP_X86    
    add_definitions(-DUCP_X86)    
    message(STATUS "build x86")    
    # Set DEPS_SOURCE_DIR to ...deps_x86
    set(DEPS_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../../deps_x86)
endif()
if(PLATFORM_AARCH64)    
    # Set output path    
    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/)    
    # Copy files    
    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/)    
    # Copy files    
    file(COPY ${DEPS_SOURCE_DIR}/ucp/bin DESTINATION ${OUTPUT_ROOT}/x86/)
endif()
# Install files (usually library files, header files, etc.) to target directory
install(FILES ${DEPS_SOURCE_DIR}/opencv/lib/libopencv_world.so.3.4 DESTINATION ${RELEASE_LIB_DIR})
# file(GLOB HLOG_LIBS "...") matches libhlog.so* and installs
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})
# Additional installation of special libraries for x86 platform
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()
# Add subproject 07_single_rotate
add_subdirectory(07_single_rotate)
  1. Where is PROJECT_SOURCE_DIR set? Answer: PROJECT_SOURCE_DIR is a predefined CMake variable that points to the root directory of the project source code, commonly used for building paths, referencing resource files, etc., helping CMake find files in the project.
  2. Where is build_type set? Answer: In the build.sh script, details can be found in the documentation:Journey 6|Why the VP Toolchain Example Can Run.

04 ConclusionThis article mainly discusses how to write CMakeLists.txt used in the VP single operator example, primarily suitable for users like me who are learning the basics of C++~

Read the original text and communicate with the author

Leave a Comment