Managing C/C++ Projects with CMake: From Basics to Library Integration
Currently, I use CMake to manage almost all of my C/C++ projects. CMake’s syntax is concise yet powerful, and most major C/C++ libraries offer built-in support for it. In my professional workflow, I frequently interact with several representative libraries:
- OpenCV: An open-source computer vision library maintained by Intel, essential for image processing.
- Boost: A collection of high-quality libraries that extend C++ functionality, including features not yet in the standard and various utility “syntactic sugar.”
- Qt: A powerful framework for building cross-platform User Interfaces.
- CUDA: NVIDIA’s parallel computing platform for GPU acceleration.
This post starts with a simple Hello CMake program and moves on to practical ways of using CMake in real-world scenarios.
1. A Minimal C/C++ Project
For the simplest possible CMakeLists.txt, you can refer to hello-cmake. Suppose we have a single source file named main.cpp:
#include <iostream>
int main(int argc, char *argv[])
{
std::cout << "Hello CMake!" << std::endl;
return 0;
}
In the project directory, create a CMakeLists.txt file with the following content:
cmake_minimum_required(VERSION 3.5)
# Set the project name
project (hello_cmake)
# Add an executable
add_executable(hello_cmake main.cpp)
Run the following commands in your terminal to compile the project:
mkdir build && cd build
cmake ..
make
Execute the binary:
./hello_cmake
If it prints Hello CMake, the compilation was successful.
2. Integrating OpenCV with CMake
How do we call OpenCV within a project? Let’s write a simple program cv-test.cpp that reads and displays an image:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
int main(int argc, char** argv )
{
if ( argc != 2 )
{
std::cout<<"usage: DisplayImage.out <Image_Path>"<<std::endl;
return -1;
}
cv::Mat image;
image = cv::imread( argv[1] );
if ( image.empty() )
{
std::cout<<"No image data"<<std::endl;
return -1;
}
cv::imshow("Display Image", image);
cv::waitKey(0);
return 0;
}
The corresponding CMakeLists.txt would look like this:
cmake_minimum_required(VERSION 2.8)
project( DisplayImage )
find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( cv-test cv-test.cpp )
target_link_libraries( cv-test ${OpenCV_LIBS} )
The key lines are:
find_package: Locates the OpenCV installation.include_directories: Adds OpenCV header files.target_link_libraries: Links the OpenCV binaries to your executable.
Note: This assumes OpenCV is correctly installed and the OpenCV_DIR environment variable is set. If you have multiple versions or want to specify a custom build (e.g., a version built with CUDA 11 support), you can set the path manually:
set(OpenCV_DIR D:/WORK/opencv-github/opencv452/build-cuda11/install)
3. Using Boost with CMake
Configuring Boost is straightforward, primarily requiring the setup of Boost_INCLUDE_DIR and Boost_LIBRARY_DIRS.
For ease of deployment, I often enable Boost_USE_STATIC_LIBS for static linking. Here is a typical configuration:
set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_MULTITHREADED ON)
# Clear cached variables (optional)
unset(Boost_LIBRARIES)
unset(Boost_INCLUDE_DIR CACHE)
unset(Boost_LIBRARY_DIRS CACHE)
# Set your specific paths
set(Boost_INCLUDE_DIR D:/Lib/boost_1_76_0)
set(Boost_LIBRARY_DIRS D:/Lib/boost_1_76_0/lib64-msvc-14.2)
# Find specific components
find_package(Boost COMPONENTS system filesystem json REQUIRED)
if(NOT Boost_FOUND)
message("Boost not found")
endif()
include_directories(${Boost_INCLUDE_DIRS})
4. Managing Qt Projects with CMake
Qt has built-in support for CMake. While Qt’s build process is complex (handling UI files, resources, and MOC), CMake makes it manageable without relying solely on QtCreator or VS plugins:
cmake_minimum_required(VERSION 3.1.0)
project(helloworld VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Enable automatic handling of Qt-specific tools
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
if(CMAKE_VERSION VERSION_LESS "3.7.0")
set(CMAKE_INCLUDE_CURRENT_DIR ON)
endif()
find_package(Qt5 COMPONENTS Widgets REQUIRED)
add_executable(helloworld
mainwindow.ui
mainwindow.cpp
main.cpp
resources.qrc
)
target_link_libraries(helloworld Qt5::Widgets)
5. CUDA Support in CMake
Since version 3.9, CMake has provided native support for CUDA. You only need to add CUDA to the LANGUAGES parameter in the project command:
project(cuda-demo VERSION 0.1.0 LANGUAGES CXX CUDA)
6. Controlling Output Directories
In projects with multiple sub-modules, CMake defaults to generating binaries within their respective subdirectory structures. To simplify debugging and access, you can force all executables and libraries to be output to a single directory:
# Set executable file output directory
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# Set library file output directory
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)