What is what in CMake 3.10+ and how to use it – InformTFB

What is what in CMake 3.10+ and how to use it

What is what in CMake 3.10+ and how to use it

Introduction

CMake is growing in popularity. Many large projects are moving from their own build tools to CMake. The Conan project offers integration with CMake for dependency management.

CMake developers are actively developing The tool and adding new features that solve common problems that arise when building projects. The transition from CMake 2 to CMake 3 was quite painful. The documentation does not cover all aspects of usage. The functionality is extremely extensive, and the difficulties that arise vary from project to project. In this article, I will talk about the tools that CMake 3.10 and higher offers. With each new version, new parts are added or old ones are improved. It is better to analyze the current state of the Changelog, since many improvements in recent versions are very specific to individual projects, such as improving support for Cuda compilers. I’ll focus on General concepts that will help you organize your C++ project in an optimal way in terms of using CMake as the main build system.

CMake offers a wide range of tools. In order not to get lost in the definitions, the article will first define entities, through which specific examples will be explained. I will duplicate the names of entities in English. Some terms do not have a clear translation into Russian.

Overview of the demonstration project

Examples of problems when building a project and recommended CMake solutions are taken from my library project, which uses neural networks to determine synchronization between speech and image.

The project is written in C++ and is cross-platform. Contains reusable components and depends on external libraries. The intended use area is integration into the final application that performs video stream analysis, so the project also contains a demo application with an example of using the API. The demo application is delivered in source codes along with a binary library.

The structure of project files and folders named ‘neuon’ is as follows:

neuon
|-sources
  |-res
  |-src
  |-CMakeLists.txt
|-headers
  |-include
  |-CMakeLists.txt
|-examples
  |-res
  |-src
  |-CMakeLists.txt
|-cmake
  |-FindTensorflow.cmake
  |-FindJsonCpp.cmake
|-res
	|-neoun-config.cmake.in
|-CMakeLists.txt

Entities and concepts used

  • Applications included in the package (CMake, CPack, CTest, CDash)
  • Predefined and expected files and their purpose
  • Modules and configurations
  • Script object model-goals, properties
  • Properties, variables, arguments, and parameters.

Applications included in the package (CMake, CPack, CTest, CDash)

CMake comes bundled with several applications designed to complement the project build process. Cmake, which works with a file describing the project, is responsible for the build itself. Using cmake consists of several steps: the generation step, the build step, and the install step.

The first step when building a project is to generate build scripts using the project description(CMakeLists.txt) in the CMake language. The result of generation is a script or set of files required to run the underlying build system(for example, Makefile or VIsual Studio Solution). CMake does not run the compiler itself, although it can be used as a proxy for generalizing calls to underlying tools.

The next step is to build the project directly from the source code. The result is binary files and other artifacts that are part of the final product for the consumer. All artifacts, despite their availability, are in a special state controlled by CMake and are usually unsuitable for moving or distributing, but can be used for debugging locally.

The final step of using the SMAC is a step in the installation. Installation involves rebuilding or linking binary artifacts, if necessary, in order to make them suitable for use on the target system. As a rule, the installation also moves artifacts to the desired locations and creates the desired layout by directory. Installation at this stage has nothing to do with installing the distribution or unpacking the archive, but if the build configuration is incorrect, it can install freshly compiled artifacts on the local system. When used, the installation step is not performed manually.

The basic functionality of CMake does not end with the console application. CMake also provides a graphical interface for managing the generation and build stages-cmake-gui. It also offers a wide range of modules(CMake modules) for use in project description files. Third-party projects can offer their own configurations(Library Configs) for easier use in conjunction with CMake.

CMake is an extremely flexible and extensible tool. And the price for this is its congestion. If the project needs some functionality during Assembly , it makes sense to study what Smake offers. Maybe the same problem has already been solved and the solution has passed community review.

The CTest application extends the build experience by providing a single interface for interacting with tests. If the project contains tests, ctest plays the role of a single trigger mechanism that generates a launch report. To use CTest as a universal executor, each test must be registered. According to its registration, the final report will include the name and result of the test execution. CTest also provides integration with the CDash dashboard for sharing test results, managing launches, grouping tests, and other functionality used in automated build pipelines. (CI/CD Dashboard).

CPack is a tool for packaging a compiled project into platform-specific packages and installers. On the one hand, CPack is universal for creating installers of the target format, on the other hand, it depends on the system where it is run, since it relies on system tools to create the installer. A single command-line format for generating an NSIs installer for Windows, a deb package for Ubuntu, and an RPM package for Centos does not imply generating an RPM when running on Ubuntu or Windows. The main advantage of CPack over individual packers is that the entire installer configuration is located next to the project, and uses the same mechanisms as CMake itself. To add a new format to the project, just define additional format-specific variables and CPack will do the rest of the work.

Predefined and expected files and their purpose.

CMake makes extensive use of the file system and manages many folders and files. CMake supports builds outside of the project location. The project location is called the source directory. During generation, CMake saves files to the build Directory. In some contexts, the build folder is also referred to as the binary directory folder, as opposed to the source folder. When performing the installation step, the Install directory starts appearing. These locations can point to the same location, or to different ones – this is controlled independently and available when used.

In addition to the file system itself, CMake uses a prefix mechanism to flexibly configure relative paths. The install prefix ((CMAKE_INSTALL_PREFIX)) defines the prefix that will be used for all relative paths used by the project after installation. . The installation prefix and installation directory can be the same. However, there may also be differences in what is used for cross-compiling or creating portable artifacts that are not tied to a specific location. Prefix-the path (CMAKE_PREFIX_PATH) has a special meaning for the generation step. As the documentation says, this is a list of paths that Smake uses in addition to its location as the root folder for finding modules and extensions. If you need to specify additional locations, use the path prefix. It does not affect the build result, but it is extremely important for finding dependencies, integrating with third-party libraries, and using modules that are not included in the CMake package or are part of the project.

CMake uses some predefined names or parts of the file name, giving these files a special meaning.

CMake support in projects is provided CMakeLists.txt a file usually located at the root of the project. There can be several of these files and they can be included in each other in various ways. The main idea of this file is that it provides the main entry point for CMake and the project description starts with it.

After generating the build folder, you can find the file CMakeCache.txt. This file contains a complete view of the entire project that CMake was able to parse and generate. This file can be edited manually to change some parameters and variables. However, during the next generation, the changes may be lost if the parameters and arguments for running the CMake utility have changed.

Files whose names end in case-sensitive Config.cmakeor -config.cmakeare CMake compatible library and dependency configuration files. (CMake config files). These files are usually distributed along with libraries and provide integration of libraries into projects that use CMake for building.

The files have names which look like Find*.cmake contain CMake modules(by CMake-modules). Modules extend the functionality of CMake, and are used in CMakeLists.txt to perform routine tasks. Modules should be used as a framework or function library when writing your own code. CMakeLists.txt

Other files with the extension .cmake assume arbitrary content written using the CMake scripting language. Projects include such files for the purpose of reusing parts of scripts.

Sometimes CMake projects may contain files with the extension .in or.in.in This is how file templates that are instantiated by CMake during generation can be named. Instantiated files are then used by the project as text artifacts, or as files that are relevant at the time of generation and build. For example, they may contain the version and build date of binary artifacts, or a CMake configuration template that will be distributed with the artifacts later.

Modules and configurations

CMake Modules and CMake configurations contain code in the CMake script. Despite the similarity, these entities are different in purpose. Modules that typically extend the CMake behavior by providing functions, macros, and algorithms for use in CMakeLists.txt, are bundled with CMake, are referred to as Find*. cmake, and are located in CMAKE_PREFIX_PATH. Modules are not tied to a specific project and should be project-independent. The project can add modules to the list as needed when generating or using them, but it is recommended that you publish modules in order to include them in the CMake distribution. Modules play the role of the “CMake Standard library”. Modules are supported and developed by CMake maintainers.

Configurations, on the other hand, are an integral part of the project.they are delivered together with header files and import libraries, and provide integration of libraries into other projects. The project configuration is not used when building a project. The configuration is the result of the project build and is included in the installer, supported and developed by the project authors.

Configurations should not be located in CMAKE_PREFIX_PATHand should follow the location and naming Convention described in the official documentation. (https://cmake.org/cmake/help/latest/command/find_package.html#full-signature-and-config-mode)

Configurations describe project dependencies to make it easier to use. Configurations are extremely sensitive to their implementation. Following the best practices and recommendations will help you create configurations that make it easier for customers and users to integrate the project.

Script object model-goals, properties

Since version 3, CMake has shifted the paradigm of its scripts from procedure-oriented to object-oriented. In version 2, the project description contained a set of function calls that configured the properties needed for Assembly and use. In version 3, variables and properties were replaced with goals. A target, in the context of CMake, is an entity that you can perform operations on, change its

its properties, and ensure that it is available and ready. Targets can include, but are not limited to, binary artifacts and project executables, project header files, additional files created during generation, target dependencies, external libraries or files, and so on.

Goals have properties that can be read(all of them) or written(many of them), for the purpose of fine-tuning and ensuring the desired result. Properties are named goal fields. They are managed by CMake based on the default values of parameters and arguments and / or the build environment, as well as managed by the project itself, depending on the purpose and desired result.

Properties, variables, arguments, and parameters.

CMake provides flexibility in configuration and usage by providing different ways to control and manage your behavior.

Goals can be controlled by their properties. The command line when running CMake utilities can contain command-line arguments passed directly to the utility in the conventional way --argumentor -f lag. The command line may also contain parameters. Parameters passed on the command line via -DNAME=VALUEor -DNAME:TYPE=VALUEare converted to variables with the same name in the body of the script. Parameters can also be environment variables or some occurrences of CMake variables in the file CMakeCache.txt. The parameters of the variables in CMakeCache.txt are virtually indistinguishable.

CMake variables are variables in the common sense of the word when writing scripts. Variables may or may not be declared. They may have a value, or they may not have a value, or they may have an empty value. In addition to variables inherited from parameters(or default values), scripts can also declare and use their own variables.

In this way, CMake utilities are managed through command-line arguments, startup parameters, variables in scripts, and target properties. Arguments do not affect the state of scripts, parameters are turned into variables with the same name, and target properties have a predefined documented name and can be set to arbitrary values by calling the corresponding functions.

Examples of tasks and using available tools

I have a project in my hands that uses CMake. What should I do with it?

Generate Assembly files in the build folder, run the build, and optionally run the installation.

cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_BUILD_TYPE=Release -Sneuon -Bcmake-release-build
cmake --build cmake-release-build -- -j8
cmake --build cmake-release-build --target install

In this example, we are building the release build of the project in a separate directory. A release in this situation involves enabling optimizations and removing debugging information from the binary artifact. CMake has 5 known values of the CMAKE_BUILD_TYPE parameter are: Debug, Release, RelWithDebInfo, MinSizeRel and None – if you do not set a specific value. We also explicitly specify CMAKE_INSTALL_PREFIX /usr / local is the default value for Unix systems, and since writing to This directory requires superuser rights, the last installation command returns an error because it cannot write files as intended. You should either run it with superuser rights(which is highly discouraged if the target platform has package managers), or change the installation prefix to a location that does not require superuser rights, or do not install the project on the system, or install it using destination reassignment. For make as an underlying system, you can use DESTDIR: cmake --build cmake-release-build --target install -- DESTDIR=$HOME/neuon

Reassigning an assignment depends on the actual build system, and not every one of them knows how to do this.

Example of a Linux build from a real project:

# Локальная сборка зависимости проекта и установка в отдельную директорию, 
# которая будет использоваться при сборке основного проекта.CMake автоматически собирает проект
# при установке по необходимости.
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=On -DCMAKE_INSTALL_PREFIX=/opt/neuon -DCMAKE_BUILD_TYPE=Release -s googletest -Bgoogletest-build
cmake --build googletest-build --target install -- DESTDIR=$PWD/deps -j8

# Сборка и упаковка в TGZ основного проекта в 8 потоков make.
cmake -DCMAKE_PREFIX_PATH=$PWD/deps -DCMAKE_INSTALL_PREFIX=/opt/neuon -DCPACK_SET_DEST_DIR=On -DCMAKE_BUILD_TYPE=Release -Dversion=0.0.0 -S neuon -B neuon-build
cmake --build neuon-build -- -j8
cd neuon-build && cpack -G "TGZ" 
# CPack на конец 2020 не поддерживает -B аргумент. Необходимо запускать в папке сборки

For generating under Windows using Visual Studio:

cmake -G "Visual Studio 16 2019" -A x64 -T host=x64 -DBUILD_SHARED_LIBS=On -DCMAKE_PREFIX_PATH=d:\msvc.env\ -DCMAKE_INSTALL_PREFIX=/neuon -DFFMPEG_ROOT=d:\msvc.env\ffmpeg -S neuon -B neuon-build

How do I run tests with CTest?

After generation, run CTest in the build folder.

ctest .

By default, CTest does not display the progress of tests. Call arguments will help you achieve the desired behavior.

How do I use goals and properties?

add_library(neuon
    ${CMAKE_CURRENT_BINARY_DIR}/generated/version.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/generated/birthday.cpp
    src/license.cpp
    src/model.cpp
    src/tensorflow_api.cpp
    src/tensorflow_dynamic.cpp
    src/tensorflow_static.cpp
    src/neuon_c.cpp
    src/neuon_cxx.cpp
    src/demo.cpp
    src/configuration.cpp
    src/speech_detection.cpp
    src/face_detection.cpp
    src/log.cpp
    )
add_library(neuon::neuon ALIAS neuon)
target_link_libraries(neuon PRIVATE Threads::Threads JsonCpp::JsonCpp Boost::headers Aquila::Aquila dlib::dlib Tensorflow::Tensorflow Boost::filesystem spdlog::spdlog PUBLIC neuon::headers )
target_compile_features(neuon PRIVATE cxx_std_17)
set_target_properties(neuon PROPERTIES CXX_EXTENSIONS OFF)
set_target_properties(neuon PROPERTIES INSTALL_RPATH "$ORIGIN")
set_target_properties(neuon PROPERTIES C_VISIBILITY_PRESET hidden)
set_target_properties(neuon PROPERTIES CXX_VISIBILITY_PRESET hidden)
set_target_properties(neuon PROPERTIES VISIBILITY_INLINES_HIDDEN On)
target_compile_definitions(neuon PRIVATE BOOST_UBLAS_INLINE= NEUON_ORIGINAL_BUILD )
target_include_directories(neuon
    PRIVATE src/
    ../depends/sdk/src
    ${CMAKE_CURRENT_BINARY_DIR}/generated
    )

install(TARGETS neuon EXPORT neuon-library
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel NAMELINK_COMPONENT devel
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
    OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel
    INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
    )
install(EXPORT neuon-library NAMESPACE neuon:: DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/neuon/cmake FILE neuon-targets.cmake COMPONENT devel)
install(FILES res/model.pb res/normale.json res/shape_predictor_68_face_landmarks.dat DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/neuon/res COMPONENT runtime)

Adding the build target-the library. We do not specify the library type – shared or static, leaving it up to the collector. Our build pipeline can collect two variants with two different generation calls. Then we add an alias target. this allows you to use the namespace in the CMake project configuration for more expressive naming of dependencies. Goal-the alias is used immediately in the project by the demo application, which can also use the configuration file. Using a namespace in the configuration file will put all targets in it. Without an alias, the demo application will have to explicitly distinguish between linking to the target of the parent project, or to a target from the configuration.

We specify a list of libraries and other dependencies to link our library to, and set various properties that affect the compilation parameters. In the last part, we specify exactly how we want to install our library – the goal installation is described by introducing an exported entity, for which properties are prescribed, such as the destination folder depending on the specific file type or goal type, or a component for dividing installers into separate packages or sets of installed files. Then you specify the installation of the exported entity and specify the namespace for the purposes used.

And finally, several files are installed as is. These are resources that are distributed as part of the project.

How do I use configuration files?

Starting with CMake 3.0, it is recommended to declare targets in the configuration files that can be used by the consumer. Before CMake 3.0, configuration files declared several variables named according to certain rules, and these variables became available to the consumer. At the moment, some projects still use only the old approach, some projects use the old approach as an addition to the new one. Sometimes there are initiatives to wrap old configurations in new ones or in CMake modules. In this example, we focus on the new approach as recommended.

find_package(FFMPEG REQUIRED COMPONENTS avcodec avformat swscale swresample)

if(NOT TARGET neuon::neuon)
    find_package(Neuon REQUIRED COMPONENTS headers neuon)
endif()

add_executable(neuon-sample
    ${CMAKE_CURRENT_BINARY_DIR}/generated/version.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/generated/birthday.cpp
    src/neuon_sample.cpp
    depends/sdk/src/extraction.cpp
    depends/sdk/src/options.cpp
    depends/sdk/src/source.cpp
    depends/sdk/src/demuxer.cpp
    depends/sdk/src/decoder.cpp
    depends/sdk/src/interruption.cpp
    depends/sdk/src/track_adapter.cpp
    depends/sdk/src/access_unit_adapter.cpp
    depends/sdk/src/resample.cpp
    )
target_link_libraries(neuon-sample PRIVATE spdlog::spdlog Threads::Threads Boost::program_options FFMPEG::avcodec FFMPEG::avformat FFMPEG::swscale FFMPEG::swresample neuon::neuon)
target_compile_features(neuon-sample PRIVATE cxx_std_11)
set_target_properties(neuon-sample PROPERTIES CXX_EXTENSIONS OFF)
target_include_directories(neuon-sample
    PRIVATE src/
    depends/sdk/src
    ${CMAKE_CURRENT_BINARY_DIR}/generated
    )

find_package searches for configuration files based on predefined path resolution rules. If this one CMakeLists.txt if it is not included in the main project, then the neuon:: neuon target will be unavailable and we need to explicitly enable the library in the standard way. Otherwise, the alias target will provide us with identical functionality and our application will be linked to the library from the same build directory.

We have already introduced an alias target in the main library project to make our project more versatile. CMakeLists.txt in the demo app project. Now, when building our demo application as part of the project build, the alias target will be used, and when built by a user, our library will be available through the name defined in the configuration, supplemented with a namespace.

How do I add my own tests to running CTest?

enable_testing()

find_package(GTest 1.8 CONFIG REQUIRED COMPONENTS gtest gmock gmock_main )
include(GoogleTest)

add_executable(ut_curl test/ut_curl.cpp src/curl.cpp)
target_link_libraries(ut_curl PRIVATE GTest::gmock GTest::gmock_main CURL::libcurl)
target_include_directories(ut_curl PRIVATE src/)

add_test(test_of_curl_wrapper ut_curl)

enable_testing() indicates to CMake that you plan to use CTest. add_test () registers the executable file being built in the project as one of the tests to run. A test can be any executable entity – an application, script, or third-party tool. CTest relies on the return code to determine whether the test was passed or not and generates a corresponding report.

enable_testing()
find_package(GTest 1.8 CONFIG REQUIRED COMPONENTS gtest gmock gmock_main )
include(GoogleTest)

add_executable(ut_curl test/ut_curl.cpp src/curl.cpp)
target_link_libraries(ut_curl PRIVATE GTest::gmock GTest::gmock_main CURL::libcurl)
target_include_directories(ut_curl PRIVATE src/)
gtest_discover_tests(ut_curl)

CMake offers a ready-made module for working with the Google Test Framework. If your tests use Googletest, then there are usually a lot of unit tests in the executable file. Registering in the standard way will not give a complete picture of running unit tests, since from the point of view of CMake – one application, one test. include (GoogleTest) connects a standard module that contains a function gtest_discover_teststhat registers all tests from the assembled test application as separate tests in CTest. The report becomes much more informative.

How do I configure CPack?

include(CPackComponent)

cpack_add_component(runtime)
cpack_add_component(devel DEPENDS runtime)
cpack_add_component(sample DEPENDS runtime devel)

set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_CONTACT "info@example.com")

set(CPACK_ARCHIVE_COMPONENT_INSTALL On)

set(CPACK_RPM_COMPONENT_INSTALL On)
set(CPACK_RPM_PACKAGE_AUTOREQ On)
set(CPACK_RPM_PACKAGE_AUTOREQPROV Off)
set(CPACK_RPM_DEVEL_PACKAGE_REQUIRES "blas, lapack, atlas, jsoncpp-devel")
set(CPACK_RPM_SAMPLE_PACKAGE_REQUIRES "ffmpeg-devel, jsoncpp-devel")

set(CPACK_DEB_COMPONENT_INSTALL On)
set(CPACK_DEBIAN_DEVEL_PACKAGE_DEPENDS "libopenblas-base, libblas3, libjsoncpp-dev, libjsoncpp1, libopenblas-dev")
set(CPACK_DEBIAN_SAMPLE_PACKAGE_DEPENDS "libavformat-dev, libavcodec-dev, libswscale-dev, libswresample-dev, libavutil-dev, libopenblas-dev")

include(CPack)

After connecting the required modules that extend CPack, you must set the values of the documented variables for each specific installer format or package. Some values are taken from CMake variables and parameters, and some can be filled in automatically when packaging is performed. After setting variables, you should connect the CPack module itself via include (CPack).

How do I write a configuration for my project?

There is an article on creating your own configuration file here.

In General terms, the configuration file describes the goals that a user can use in their project. The list of goals is generated by CMake when creating an exported entity linked to goals. General-purpose configuration versioning and integrity checks are implemented in the CMakePackageConfigHelpers module.

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
configure_package_config_file(res/neuon-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/neuon-config.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_DATAROOTDIR})
write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/neuon-config-version.cmake VERSION ${version} COMPATIBILITY SameMajorVersion)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/neuon-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/neuon-config-version.cmake DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/neuon/cmake COMPONENT devel)

The configuration template can be similar to:

cmake_policy(PUSH)
cmake_policy(VERSION 3.10)

@PACKAGE_INIT@

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/modules/")

include(CMakeFindDependencyMacro)
find_dependency(Tensorflow)

include ( "${CMAKE_CURRENT_LIST_DIR}/neuon-headers-targets.cmake" )
include ( "${CMAKE_CURRENT_LIST_DIR}/neuon-targets.cmake" )

check_required_components(headers)
check_required_components(neuon)

list(REMOVE_AT CMAKE_MODULE_PATH -1)

cmake_policy(POP)

How do I use other project configurations?

A properly prepared configuration does not require any additional actions to use.

find_package(Neuon REQUIRED COMPONENTS headers neuon)
target_link_libraries(neuon-sample PRIVATE neuon::neuon)

The configuration declares which libraries are available for use, which additional dependencies need to be linked to the final result, and which compiler flags should be set when using it. Where the header files and import libraries are located is also specified using the configuration file and saves the user from manual labor.

How do I use standard CMake modules?

Despite the regulated naming of modules, their use in their projects is twofold. Many standard CMake modules are connected via the include () function, but many modules that search for libraries that do not support CMake themselves through configuration rely on find_package() in module mode.

include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
include(CPackComponents)
include(CPack)
include(GoogleTest)

find_package(CURL)
find_package(Boost 1.70 MODULE REQUIRED COMPONENTS program_options log)

The Boost library, starting with version 1.70, provides CMake support via configurations. The smake module is backward compatible and can resolve the location of Boost of any version, using configurations if available and creating alias targets otherwise.

How do I use modules from third-party or personal sources?

There are often cases when a standard module is not available, or its state does not meet the needs of the project. In this case, the project can provide its own CMake modules for use in its own configurations. The key point for using your own modules is the parameter CMAKE_MODULE_PATH that lists the search path of modules. The project or project configuration can independently change the variable derived from this parameter. For example, you can add a module to your project that wraps all unnecessary compilation flags for using FFMpeg in a CMake target and link your application to these targets without worrying about linking flags, compiler flags, and all dependencies.

set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_CURRENT_SOURCE_DIR}/depends/sdk/cmake")

project(neuon-sample VERSION ${version})

find_package(Threads REQUIRED)
find_package(spdlog REQUIRED)
find_package(Boost 1.70 REQUIRED COMPONENTS program_options)
find_package(FFMPEG REQUIRED COMPONENTS avcodec avformat swscale swresample)

add_executable(neuon-sample src/neuon_sample.cpp)
target_link_libraries(neuon-sample PRIVATE spdlog::spdlog Threads::Threads Boost::program_options FFMPEG::avcodec FFMPEG::avformat FFMPEG::swscale

When you change CMAKE_MODULE_PATHyour configurations, you can use the list functions to avoid conflicts between modules and the user’s project. After adding your own paths to the variable and using them, you can remove these occurrences from the list.

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/modules/")

include(CMakeFindDependencyMacro)
find_dependency(Tensorflow)

list(REMOVE_AT CMAKE_MODULE_PATH -1)

How do I connect and reuse other pieces of CMake scripts?

CMake allows you to include parts of scripts in each other as-is, without doing any extra work. This can be useful for reusing functionality when using a module is not justified, and you want to avoid duplication. include() will include the contents of the specified file in the using file. Included files must be reentrant and not change the global state.

include(depends/sdk/cmake/version.cmake)
configure_version(neuon)
configure_birthday(neuon)

Conclusion

CMake 3.10+ clearly relies on the object usage model. Everything has a purpose. The goal has properties. The project should not change the global state. The project describes its goals, and uses the goal properties to modify its behavior. Modules-extend the functionality of CMake. Configurations provide an interface for integrating projects and dependencies. Reuse of the functionality is available and recommended.

Global properties, parameters, and variables are managed by CMake. The project should not change them at its own discretion. The project must adapt to the global state.

The Assembly is configured and modified not inside scripts, but using parameters and arguments when running CMake. Let the future project author decide whether the library is shared or static, whether it is installed in system folders or in restricted user directories, whether to enable maximum optimization, or whether to provide debugging information.

CMake offers a very wide range of available parameters and variables, so you should not duplicate them in your project.

Relative paths are preferred over absolute paths.

CMake projects can be included in each other in any order. One project should not destroy the state created by another.

CMake is distributed as a self-contained archive for all popular platforms. The configuration file is limited to the consumer version of CMake, but to build and package a project, you can bring the latest version, deploy it locally as part of the build environment, and use the latest functionality that is made to make the life of project authors easier.

Useful links

Official CMake documentation

Community-supported wiki with the participation of CMake authors

Official CMAKE distribution page

What is the difference between different ways to include subprojects?

CMake offers different ways to connect projects to each other. If we assume that the project is described by at least one CMakeLists.txt, then projects can be linked to each other via add_subdirectory(), include(), ExternalProject_Add().

The basic idea is that CMakeLists.txt describes the project as something named and versioned. The project () Directive sets some of the variables used by CMake during generation and Assembly. At least the version of the project.

include () includes the content as-is, and in one CMakeLists.txt there can’t be two active project () directives.

ExternalProject_Add () offers a way to build a subproject from source, but does not make the subproject part of the main project build. Externalproject_Add is usually used to connect third-party dependencies that cannot be satisfied with the build environment or target system.

add_subdirectory () is the only correct way to connect a project to a project, creating a single tree of dependencies and relationships between goals, while maintaining project identity and self-sufficiency. In addition to directly connecting the project, CMake performs additional operations to isolate subprojects and scopes.

At the same time, you can find examples of adding CMakeLists.txt go to each level of nesting of folders – the root folder, a subfolder with source code files, in subfolders even deeper – and include each of them in each other. In practice, there is no benefit from this approach. Logical selection of subprojects with their own content CMakeLists.txt, and the root CMakeLists.txt including subprojects while performing project-wide packaging operations results in elegantly delimited projects in multi-project build systems like Visual Studio. Have one MSVS solution(solution, ugh….) with projectsprojects for building header files, libraries, and demo applications is much more pleasant than having a dozen artificial projects without a clearly defined purpose.

Valery Radokhleb
Valery Radokhleb
Web developer, designer

Leave a Reply

Your email address will not be published. Required fields are marked *