How to write third-party library dependencies
This section focuses specifically on how to install third-party libraries, how to find them in CMake, and how to place them in the template's Third-party dependencies section.
The principle of this template is:
# ========================
# Third-party dependencies
# ========================
find_package(Eigen3 REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PUBLIC
Eigen3::Eigen
)
That is: if the communication parameters (like baud rate) set in the microcontroller program are not consistent with those configured in the serial port software on the PC, the communication between the microcontroller and the computer will fail.
- Who uses third-party libraries, writes
find_package. - Third-party dependencies are centralized in the
Third-party dependenciessection of this module. - Prioritize linking modern CMake targets, such as
Eigen3::EigenandOpenCV::opencv_core. - Don't dump all third-party libraries at the top level
CMakeLists.txt.
find_package Basic Grammar
find_package(<PackageName> [version] [REQUIRED] [COMPONENTS components...])
common practice
find_package(Eigen3 REQUIRED)
find_package(OpenCV REQUIRED)
find_package(Boost REQUIRED COMPONENTS system filesystem)
find_package(PCL REQUIRED COMPONENTS common io)
Parameter Description:
| parameter | effect |
|---|---|
<PackageName> | Package name, such as Eigen3, OpenCV |
version | Minimum required version or exact version |
REQUIRED | report an error if not found |
COMPONENTS | Only search for certain components |
REQUIRED
find_package(Eigen3 REQUIRED)
If Eigen is not found, the CMake configuration stage will fail immediately.
If I don't write
find_package(Eigen3)
then you need to judge for yourself:
if(Eigen3_FOUND)
target_link_libraries(my_target PUBLIC Eigen3::Eigen)
endif()
Engineering templates commonly contain REQUIRED because when dependencies are missing, it's better to fail early.
COMPONENTS
Libraries such as OpenCV, Boost, and PCL usually feature multiple modules.
For example:
find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui)
indicates only:
opencv_coreopencv_imgprocopencv_highgui
This is clearer than importing all of OpenCV directly.
In target_link_libraries, dependency visibility
When linking third-party libraries, also consider PUBLIC, PRIVATE, and INTERFACE.
| Keywords | When to use |
|---|---|
PRIVATE | Third-party libraries are only used within .cpp, for example, headers do not expose it. |
PUBLIC | The header file includes third-party library types, so users need to be aware of them as well. |
INTERFACE | The current target does not compile itself, it only passes downstream. |
For example, if the header file for lib1 is just like this:
#pragma once
namespace lib1 {
void run_eigen_vector_example();
}
If the header file does not expose Eigen types, Eigen can be used with PRIVATE:
target_link_libraries(${PREFIX}_src_lib
PRIVATE
Eigen3::Eigen
)
If the header file is written as:
#pragma once
#include <Eigen/Dense>
namespace lib1 {
Eigen::Vector3d make_vector();
}
Therefore, when downstream includes this header file, it also requires the Eigen include path, so PUBLIC should be used:
target_link_libraries(${PREFIX}_src_lib
PUBLIC
Eigen3::Eigen
)
This template example uses PUBLIC to demonstrate dependency propagation; in actual projects, you can choose based on whether the header file exposes third-party types.
Eigen3
Eigen is a commonly used linear algebra library, primarily a header-only library.
Install
Ubuntu/Debian:
sudo apt install libeigen3-dev
Fedora:
sudo dnf install eigen3-devel
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(Eigen3 REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PUBLIC
Eigen3::Eigen
)
Using C++
#include <Eigen/Dense>
Eigen::Vector3d v(1.0, 2.0, 3.0);
Visibility recommendations
| Scene | Linking method |
|---|---|
Eigen is only used in .cpp. | PRIVATE Eigen3::Eigen |
The header file exposes Eigen::Matrix and Eigen::Vector. | PUBLIC Eigen3::Eigen |
OpenCV4
OpenCV is a computer vision library with many modules; it's recommended to introduce by components.
Install
Ubuntu/Debian:
sudo apt install libopencv-dev
Fedora:
sudo dnf install opencv-devel
Common Components
| component | effect |
|---|---|
core | Basic data structures, such as cv::Mat |
imgproc | image processing |
imgcodecs | Read and write images |
highgui | Simple window display |
videoio | Camera and Video I/O |
calib3d | Camera calibration, geometry |
features2d | feature points |
dnn | DNN inference module |
CMake Writing: Recommended to Organize by Component
# ========================
# Third-party dependencies
# ========================
find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs highgui)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
opencv_core
opencv_imgproc
opencv_imgcodecs
opencv_highgui
)
Some OpenCV installations provide imported targets like OpenCV::opencv_core which you can also use.
target_link_libraries(${PREFIX}_src_lib
PRIVATE
OpenCV::opencv_core
OpenCV::opencv_imgproc
OpenCV::opencv_imgcodecs
OpenCV::opencv_highgui
)
If your system doesn't have these OpenCV:: targets, then use the previous opencv_core format.
CMake Approach: Simple and Brute-Force Version
find_package(OpenCV REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
${OpenCV_LIBS}
)
This approach works, but it's not as clear as the component-based method. For tutorials, I recommend prioritizing component-based writing.
Using C++
#include <opencv2/opencv.hpp>
cv::Mat image = cv::imread("test.png");
Boost
Boost is a large collection of C++ libraries. Here are system and filesystem as examples.
Install
Ubuntu/Debian:
sudo apt install libboost-all-dev
Fedora:
sudo dnf install boost-devel
If you only want to install a few components, Ubuntu/Debian also allows you to install similar ones on demand:
sudo apt install libboost-system-dev libboost-filesystem-dev
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(Boost REQUIRED COMPONENTS system filesystem)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
Boost::system
Boost::filesystem
)
Using C++
#include <boost/filesystem.hpp>
boost::filesystem::path p{"."};
Note: If using C++17's std::filesystem, Boost.Filesystem is often unnecessary in many scenarios.
PCL
PCL is the point cloud library, commonly used in robotics, 3D perception, and SLAM projects.
Install
Ubuntu/Debian:
sudo apt install libpcl-dev
Fedora:
sudo dnf install pcl-devel
Common Components
| component | effect |
|---|---|
common | point type, basic tools |
io | Reading and writing PCD/PLY files and similar formats. |
filters | filtering |
features | feature |
registration | registration |
segmentation | Division |
visualization | Visualization |
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(PCL REQUIRED COMPONENTS common io filters)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
${PCL_LIBRARIES}
)
target_include_directories(${PREFIX}_src_lib
PRIVATE
${PCL_INCLUDE_DIRS}
)
target_compile_definitions(${PREFIX}_src_lib
PRIVATE
${PCL_DEFINITIONS}
)
Some PCL versions also provide imported targets. If your environment supports them, you can prioritize using them as follows:
target_link_libraries(${PREFIX}_src_lib
PRIVATE
PCL::common
PCL::io
PCL::filters
)
The actual selection is based on the results provided by the local service find_package(PCL ...).
fmt
fmt is a formatted output library, and the style of C++20 std::format also derives from it.
Install
Ubuntu/Debian:
sudo apt install libfmt-dev
Fedora:
sudo dnf install fmt-devel
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(fmt REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
fmt::fmt
)
Using C++
#include <fmt/core.h>
auto s = fmt::format("value = {}", 42);
spdlog
spdlog is a commonly used logging library.
Install
Ubuntu/Debian:
sudo apt install libspdlog-dev
Fedora:
sudo dnf install spdlog-devel
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(spdlog REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
spdlog::spdlog
)
Using C++
#include <spdlog/spdlog.h>
spdlog::info("hello {}", "spdlog");
yaml-cpp
yaml-cpp is commonly used to read configuration files.
Install
Ubuntu/Debian:
sudo apt install libyaml-cpp-dev
Fedora:
sudo dnf install yaml-cpp-devel
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(yaml-cpp REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
yaml-cpp::yaml-cpp
)
Some older environments may have the target name yaml-cpp. If yaml-cpp::yaml-cpp is not present, adjust according to the error message.
nlohmann_json
nlohmann_json is a commonly used JSON library, typically a header-only library.
Install
Ubuntu/Debian:
sudo apt install nlohmann-json3-dev
Fedora:
sudo dnf install json-devel
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(nlohmann_json REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
nlohmann_json::nlohmann_json
)
Using C++
#include <nlohmann/json.hpp>
nlohmann::json data;
data["name"] = "cmake_template";
Threads
The C++ standard threading library typically does not require installing additional packages, but it is recommended to use CMake's Threads package during linking.
Install
Generally, no separate installation is required.
If the compiler toolchain is missing:
Ubuntu/Debian:
sudo apt install build-essential
Fedora:
sudo dnf install gcc gcc-c++ make
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(Threads REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
Threads::Threads
)
Using C++
#include <thread>
std::thread worker([] {
// do work
});
worker.join();
OpenMP
OpenMP is a programming interface for multi-threaded parallel computing in shared-memory systems. It allows developers to parallelize code using pragmas and runtime library routines.
Install
Ubuntu/Debian:
sudo apt install libomp-dev
Fedora:
sudo dnf install libgomp
CMake syntax
# ========================
# Third-party dependencies
# ========================
find_package(OpenMP REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
OpenMP::OpenMP_CXX
)
Using C++
#include <omp.h>
#pragma omp parallel for
for (int i = 0; i < 100; ++i) {
// parallel work
}
Sophus
Sophus is a Lie group and algebra library commonly used in SLAM and robot pose calculations.
Install
Often, distribution repositories don't provide the appropriate version, so the common practice is to install from source or rely on dependency management by ROS or the project.
If system repositories are available, you can try searching:
apt search sophus
dnf search sophus
CMake syntax
If already installed and providing CMake config:
# ========================
# Third-party dependencies
# ========================
find_package(Sophus REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PRIVATE
Sophus::Sophus
)
Sophus often depends on Eigen. If your code also directly uses Eigen, you can explicitly write:
find_package(Eigen3 REQUIRED)
find_package(Sophus REQUIRED)
target_link_libraries(${PREFIX}_src_lib
PUBLIC
Eigen3::Eigen
PRIVATE
Sophus::Sophus
)
Common dependencies quick reference
| library | Ubuntu/Debian | Fedora | CMake search | Common Link Targets |
|---|---|---|---|---|
| Eigen3 | libeigen3-dev | eigen3-devel | find_package(Eigen3 REQUIRED) | Eigen3::Eigen |
| OpenCV | libopencv-dev | opencv-devel | find_package(OpenCV REQUIRED COMPONENTS ...) | opencv_core etc. |
| Boost | libboost-all-dev | boost-devel | find_package(Boost REQUIRED COMPONENTS ...) | Boost::system etc. |
| PCL | libpcl-dev | pcl-devel | find_package(PCL REQUIRED COMPONENTS ...) | ${PCL_LIBRARIES} or PCL::common |
| fmt | libfmt-dev | fmt-devel | find_package(fmt REQUIRED) | fmt::fmt |
| spdlog | libspdlog-dev | spdlog-devel | find_package(spdlog REQUIRED) | spdlog::spdlog |
| yaml-cpp | libyaml-cpp-dev | yaml-cpp-devel | find_package(yaml-cpp REQUIRED) | yaml-cpp::yaml-cpp |
| nlohmann_json | nlohmann-json3-dev | json-devel | find_package(nlohmann_json REQUIRED) | nlohmann_json::nlohmann_json |
| Threads | Usually no need to install separately | Usually no need to install separately | find_package(Threads REQUIRED) | Threads::Threads |
| OpenMP | libomp-dev | libgomp | find_package(OpenMP REQUIRED) | OpenMP::OpenMP_CXX |
Package names may vary slightly across different distributions and versions. If installation fails, first try using:
apt search <关键字>
dnf search <关键字>
Confirming the package name.
Incorporating both Eigen and OpenCV into a single module involves handling dependencies for two distinct, yet often complementary, libraries. Eigen is primarily used for linear algebra and matrix operations, while OpenCV focuses on computer vision and image processing.
Here’s a general guide for integrating them into a C++ or Python project:
1. Environment Setup & Installation
- C++: Both libraries must be installed and findable by your build system. You can install them via system package managers (e.g.,
apt,brew) or build them from source. Most modern C++ projects use a package manager like vcpkg or Conan, or rely on CMake to locate them viafind_package(). - Python: Use pip for installation:
pip install numpy # Eigen is typically used via NumPy bindings in Python pip install opencv-python
2. Project Configuration (CMake Example)
For a C++ project using CMake, your CMakeLists.txt would include directives to find and link both libraries:
cmake_minimum_required(VERSION 3.10)
project(MyProject)
# Find the packages
find_package(Eigen3 3.3 REQUIRED)
find_package(OpenCV 4.0 REQUIRED)
# Define your executable or library
add_executable(my_module main.cpp)
# Link against both
target_link_libraries(my_module PRIVATE ${OpenCV_LIBS} Eigen3::Eigen)
# Ensure include directories are available
target_include_directories(my_module PRIVATE
${OpenCV_INCLUDE_DIRS}
# Eigen's include directory is handled automatically by the imported target
)
3. Code Usage (C++ Example)
In your source code, you can freely mix types and functions from both libraries. A common use case is converting data between OpenCV's cv::Mat and Eigen's matrices.
#include <opencv2/opencv.hpp>
#include <Eigen/Dense>
#include <iostream>
int main() {
// 1. Create an OpenCV image
cv::Mat cv_img(3, 3, CV_64F, cv::Scalar(0));
cv_img.at<double>(0, 0) = 1.0;
// 2. Convert OpenCV Mat to Eigen Matrix
Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
eigen_map(cv_img.ptr<double>(), cv_img.rows, cv_img.cols);
Eigen::MatrixXd eigen_matrix = eigen_map;
// 3. Perform an Eigen operation (e.g., matrix multiplication)
Eigen::MatrixXd result = eigen_matrix * eigen_matrix.transpose();
// 4. Convert the result back to OpenCV Mat for display/saving
cv::Mat cv_result(result.rows(), result.cols(), CV_64F);
Eigen::Map<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
result_map(cv_result.ptr<double>(), result.rows(), result.cols());
result_map = result;
// Use OpenCV to print or process the result
std::cout << "OpenCV Mat result:\n" << cv_result << std::endl;
return 0;
}
4. Code Usage (Python Example)
In Python, the integration is seamless because both libraries use NumPy arrays as their primary dense data structure.
import numpy as np
import cv2
# 1. Create a NumPy array (Eigen-like linear algebra via NumPy)
eigen_matrix = np.array([[1.0, 2.0], [3.0, 4.0]])
# 2. Use it directly with OpenCV
# OpenCV functions accept NumPy arrays
processed = cv2.GaussianBlur(eigen_matrix.astype(np.float32), (5, 5), 0)
# 3. Perform Eigen/NumPy-style operations
result = np.linalg.inv(eigen_matrix) # Matrix inversion
print("NumPy array result:\n", result)
Key Points:
- C++: The primary challenge is ensuring both libraries are correctly found and linked by the build system. Using modern CMake targets (
Eigen3::Eigen,${OpenCV_LIBS}) is the recommended approach. - Python: The ecosystem is unified around NumPy arrays. Conversion between
cv::Matand Eigen matrices is straightforward in C++, and nearly automatic in Python. - Type Conversion: Be mindful of data types (e.g.,
CV_64Fin OpenCV corresponds todoublein Eigen). Ensure matrices are in a compatible format (like row-major for OpenCV and Eigen'sRowMajormapping). - Documentation: Refer to the official tutorials for both libraries: Eigen Tutorials and OpenCV Tutorials.
This combination is powerful for robotics, computer vision, and scientific computing projects where efficient linear algebra is needed alongside image processing.
For example lib1 simultaneously using Eigen and OpenCV:
# ========================
# Third-party dependencies
# ========================
find_package(Eigen3 REQUIRED)
find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs)
target_link_libraries(${PREFIX}_src_lib
PUBLIC
Eigen3::Eigen
PRIVATE
opencv_core
opencv_imgproc
opencv_imgcodecs
)
If the header file does not expose Eigen types, you can also change Eigen to PRIVATE:
target_link_libraries(${PREFIX}_src_lib
PRIVATE
Eigen3::Eigen
opencv_core
opencv_imgproc
opencv_imgcodecs
)
Not recommended way of writing
Not Recommended: Top-level centralized search for all libraries
# 顶层 CMakeLists.txt
find_package(Eigen3 REQUIRED)
find_package(OpenCV REQUIRED)
find_package(PCL REQUIRED)
Question
- The top level is getting messier.
- Not sure which module actually uses which library.
- When deleting modules, it's easy to accidentally miss dependencies.
- Newcomers struggle to understand dependencies when reading projects.
Not Recommended: Global Include
include_directories(${OpenCV_INCLUDE_DIRS})
link_libraries(${OpenCV_LIBS})
Question
- Affects all targets.
- Dependencies are unclear.
- This could accidentally introduce include ordering issues.
Modern CMake Recommendations:
target_link_libraries(my_target
PRIVATE
opencv_core
)
If a library provides an imported target, include paths and link libraries will automatically propagate with the target.
library not found in find_package
Step 1: Check if system packages are installed.
dpkg -l | grep eigen
rpm -qa | grep eigen
Step 2: Check that CMake can detect the config file
Common file names:
Eigen3Config.cmake
OpenCVConfig.cmake
PCLConfig.cmake
fmtConfig.cmake
Step 3: Manually Specify Search Path
If the library is installed in a non-standard directory, you can add the following during configure:
cmake --preset linux-debug -DCMAKE_PREFIX_PATH=/opt/some_library
or add in the preset:
"CMAKE_PREFIX_PATH": "/opt/some_library"
Multiple paths can be separated by semicolons:
"CMAKE_PREFIX_PATH": "/opt/lib_a;/opt/lib_b"
Step 4: Check CMake error messages
CMake will usually tell you which config file is missing, for example:
Could not find a package configuration file provided by "OpenCV"
First, check in this case:
- Whether the development package is installed.
- Is the package name correct?
- Whether it was installed to a non-standard path.