Joseph Sibony
reading time:
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:
The <package>Config.cmake file would be present under the directory which allows CMake dependencies to be resolved.
And you get the desired 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 extensions
set(CMAKE_CXX_EXTENSIONS FALSE)
# source files for CMake std library
set(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!
Table of Contents
Shorten your builds
Incredibuild empowers your teams to be productive and focus on innovating.