EN ·
🌏 中文

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:

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:

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)      

References