Working With CMake Dependencies (Redundancy or Dependency? Your Choice!)

Blue-pill-red-pill

Joseph Sibony

reading time: 

4 minutes

Well, dear reader, you chose the red pill and wanted to see how deep the CMake dependencies’ rabbit hole goes? Read on then!

Any complex software will have its dependencies – be it system API calls or other libraries calls either statically or dynamically linked to it. As a build system generator CMake will help you manage these dependencies in the most natural way possible.

Case 1: Developer Dealing With CMake Dependencies for Standard Software

A developer will know which dependencies are required to compile the project. In the CMakeLists.txt file, the developer marks such standard packages as required. For example, if OpenCV is a package without which the project won’t compile, it will be marked as follows:

find_package(OpenCV REQUIRED)

Note: You can check out my blog post on working with OpenCV and CMake for a better understanding.

As soon as a required package is found, you also would get the associated header file directories and other settings. For example, if boost library is a requirement for your project here is how you would structure your CMakeLists.txt file:

cmake_minimum_required (VERSION 3.8)project(cmake_boost_demo)
find_package(Boost REQUIRED COMPONENTS date_time)include_directories(${Boost_INCLUDE_DIR})link_directories(${Boost_LIBRARY_DIRS})set(Boost_USE_STATIC_LIBS        OFF)set(Boost_USE_MULTITHREADED      ON)set(Boost_USE_STATIC_RUNTIME     OFF)set(BOOST_ALL_DYN_LINK           ON)
add_executable(${PROJECT_NAME} main.cpp)target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES})

If the required package is not found, then the build fails and you should help CMake resolve the dependency. This can be done in two ways:

  • Using CMAKE_PREFIX_PATH
  • Using specific DIR path that contains <package>config.cmake

Let me show you both ways here using a simple example that uses boost. Here is the main.cpp file:

#include <iostream>#include <boost/date_time/posix_time/posix_time_types.hpp>using namespace std;namespace pt = boost::posix_time;
int main(int argc, char** argv) {    pt::ptime now = pt::second_clock::local_time();    cout << "Year : " << (int)now.date().year() << " "        << "Month : " << (int)now.date().month() << " "        << "Day :" << (int)now.date().day() << endl;       return 0;}

Using the above CMakeLists.txt file, if we issue a build we get:

cmake -S. -BBuild .Could NOT find Boost (missing: Boost_INCLUDE_DIR)

Using the CMAKE_PREFIX_PATH approach we issue:

              cmake -S. -BBuild . -DCMAKE_PREFIX_PATH=D:\boost_1_75_0

Now we see that the generation is successful (assuming you have installed boost under D:\boost_1_75_0 directory)

-- Found Boost: D:/boost_1_75_0 (found version "1.75.0") found components: date_time

Using the Boost_DIR approach we issue a build like below:

              cmake -S. -BBuild . -DBoost_DIR=D:\boost_1_75_0\lib64-msvc-14.2\cmake\Boost-1.75.0

If you look under the D:\boost_1_75_0\lib64-msvc-14.1\cmake\Boost-1.75.0 folder you will find:

CMake dependencies_boost

 

The <package>Config.cmake file would be present under the directory which allows CMake dependencies to be resolved.

And you get the desired output:

CMake boost output

Case 2: Developer Dealing With CMake Dependencies for Internal Libraries

If the project internally has libraries on which the top-level project depends, then you can split the CMakeLists.txt into multiple smaller files with a top-level CMakeLists.txt that specifies dependencies. This can be done using:

  • add_library + target_link_libraries
  • add_subdirectory

In modern CMake, add_dependencies option is rarely used and hence I am not adding CMake add_dependencies to the above list. Let us see how add_subdirectory is used to add a dependency.

The best example I could find was CMake using itself to build. You can check out their CMakeLists.txt file here: https://github.com/Kitware/CMake/blob/master/CMakeLists.txt

You will see 20 instances of add_subdirectory the first of which gives a clear picture:

# Build CMake std library for CMake and CTest.  set(CMAKE_STD_LIBRARY cmstd)  add_subdirectory(Utilities/std)

If you check out the CMakeLists.txt file under Utilities/Std folder, you will find this:

# To ensure maximum portability across various compilers and platforms# deactivate any compiler extensionsset(CMAKE_CXX_EXTENSIONS FALSE)
# source files for CMake std libraryset(SRCS cm/bits/fs_path.cxx         cm/bits/string_view.cxx         cm/filesystem         cm/memory         cm/optional         cm/shared_mutex         cm/string_view         cm/utility         cmext/string_view)
add_library(cmstd STATIC ${SRCS})

This clearly shows how CMAKE_STD_LIBRARY cmstd is built. You can check out my blog post, CMake_OpenCV_And_UnitTests,  to find an example on how to use target_link_libraries.

Note: For example usage for CMake add_dependencies, you can check out https://github.com/spurious/SDL-mirror/blob/master/test/CMakeLists.txt. As can be seen from the first line, this does not use modern CMake.

Conclusion:

This is how far the rabbit hole goes if you are looking for CMake C++ dependencies as a developer. In this blog post, I took a deep look into how CMake dependencies are handled as a user. Hope you found the post useful!

Shorten your builds

Incredibuild empowers your teams to be productive and focus on innovating.