CMake has followed the C++ standard on the road to modernization, which leads to simpler package and dependency management. As opposed to the old ways of doing CMake like setting CMAKE_CXX_FLAGS directly, modern cmake introduces lots of facilities to handle dependencies more cleanly. But also like C++, CMake is a huge monster now which is very hard to tame. Although there are a few talks and tutorials about modern CMake on the Internet, I still find them hard to follow for the first time.

  1. Cmake_install_rpath_use_link_path
  2. Cmake_install_includedir
  3. Cmake Install Linux
  4. Cmake_install_libdir
  5. Cmake_install_libdir
  6. Cmake_install_rpath

If your workflow includes project installation, you may want to use the CMake install command that generates installation rules. CMake install invokes building targets, thus you don't need to call the Build action separately. Configure and run installation. Place the install commands into your CMakeLists.txt files.


The install command generates a file, cmakeinstall.cmake, inside the build directory, which is used internally by the generated install target and by CPack. You can also invoke this script manually with cmake-P. This script accepts several variables: COMPONENT. This article provides a straightforward set of “Hello World!” introductions to using CMake for building C projects. All steps are performed using Linux on the BeagleBone platform, but the instructions are relevant to most Linux platforms.

Here I’ll present a detailed explaination on how to use modern CMake, especially for library developers who want to package their libraries for downstream developers to use easily with CMake. Basic knowledge of CMake is preferred, as I’ll not cover too much on some basic commands.

The example code in this blog post is an simplified version of my project yart, stripping off the real files and 3rd-party dependencies. If you’d like to see a working example, you could try the code itself, there are only two external dependencies you need to install for it to work.

Now suppose we are building a library yart, and the project is structured like bellow,

Let’s start with the parent level CMakeLists.txt. Nothing interesting here, I’d like to use C++17 so CMake 3.10 is required. The project command will create a YART project as well as versioning variables including YART_VERSION, YART_VERSION_MAJOR, YART_VERSION_MINOR and YART_VERSION_PATCH.

Targets are the main objects CMake manipulate for building a project. Your library is a target, your executable is a target, and you’ll also meet some other types of targets when setting up the build system.

And each target has a set of properties attached to them, in an OO sense that they even have access control. You’ll soon notice that many commands in CMake has a signature similar to command(your-target [PUBLIC INTERFACE PRIVATE] properties). An INTERFACE property means that a user will need to respect this property when depending on this target, while a PRIVATE property means that this property is only used internally. And PUBLIC means both.


Create a Target

Without further ado, let’s set up our library first in src/CMakeLists.txt.

You might have seen or have used file(GLOB ...) before, please be advised that you should explicitly list the source files like the example above for the build system to automatically reconfigure CMake when you add a new source file.

This line enables you to use yart::yart in the example target, will see late

Configure the Target


We would like the users to choose whether to build a shared library or a static one,

There’re a lot of interesting thing going on here. In the first command, BUILD_SHARED_LIBS is read by CMake to switch between static and shared library, and a user could alter this option in cache.

Well, the generate_export_header command creates a header file which helps switch between building shared and static libraries. And here is the generated common.h file with msvc, and you should use these macros to export your library symbols like void YART_API my_api_fcn();

Cmake Install Linux

The pattern $<:> you see earlier is generator-expressions which works just like if statement but could be compactly inserted into other cmake commands like this target_compile_definitions command. Notice the PUBLIC keyword here, it says that this definition is a public property of yart.

And now, we’d like to configure the compiler options,

With target_compile_features, you could directly demand a language standard version like I did, or you could require specific c++ features like target_compile_feature(yart PUBLIC cxx_const_expr). The second command specify compile options depending on the compilers and build type, again with generator-expressions.

The target_include_directories command set up the include directories of yart. Public api is located in $<CMAKE_SOURCE_DIR>/include/, as well as the generated common.h file, and the private header file is in the same directory as $<CMAKE_CURRENT_SOURCE_DIR>. Notice that $<INSTALL_INTERFACE:include> is needed for users to find yart headers after installing yart onto their system. The include directory is a relative path to ${CMAKE_INSTALL_PREFIX} which is often /usr/local on Linux and C:Program Files on Windows.

And we would also like to organize the build tree a bit and configure where to output generated binaries,



Handle 3rd Party Dependencies

Suppose we have two 3rd-party dependencies,

Since libmodern is written with modern CMake as well, we could simply do,

And that’s it, modern CMake could handle target dependencies transitively, which means that you could forget about the messy variables and every property needed to use libmodern is handled correctly.

On the other hand, liblegacy is too old for this, so you have to switch to the old method,


And be alert that the public include requirement is not handled transitively so that yart users won’t know anything about it, yet. We’ll fix that later on. Notice you could also write a FindLibLegacy.cmake file for it and handle all sorts of usage requirements there, and finally export only a liblegacy::liblegacy target. You can find a decent example here.

Install and Export the Target

Everything should be able to compile by now. But the library is not readily available for other developers to use right now, after they hit make install and write find_package(yart) in their CMakeLists.txt.

First, we need to ensure everything is installed to the correct places on system.


GUNInstallDirs is a cross-platform solution to install directories. And we issue the install command to install our files. It has several signatures, in the above block are install(TARGETS yart ...), which installs the compiled library, and install(DIRECTORY ...), which installs the include directory to the right place. Note EXPORT yart-targets line also exports this target to be used later, and the INCLUDE DESTINATION ${LIBLEGACY_INCLUDE_DIRS} line injects the include dependencies into yart-targets so that our dependency problem mentioned above is solved here.

Talking about EXPORT, installing an EXPORT target will generate a yart-targets.cmake file which contains essential commands to build with yart correctly, like bellow,

What’s more, the find_package command expects a FindYART.cmake file or a yart-config.cmake (YARTConfig.cmake) to find this library. The first method is an old way to hell so we definitely want the second one,

The configure_package_config_file command reads an scaffolding file and creates a yart-config.cmake file with the right paths. And let’s see what the .in file looks like,

Quite simple right? You need to call @[email protected] first, find dependencies and finally include the generated yart-targets.cmake file.

A version file is also preferred by users and could be created easily with write_basic_package_version_file. Notice that these files are generated in your build tree (${CMAKE_BINARY_DIR}) and you need to install them into your system. The install directory is ${CMAKE_INSTALL_LIBDIR}/cmake/yart which is the default search directory of find_package.

One last step, remember that we also have another example alongside this library? We need to call the export command,

So that example could refer to yart without finding the package.

example/CMakeLists.txt couldn’t be more simple,

While using yart from an outside project requires one more magic touch,

That’s it folks. It’s still quite tricky to do everything right, but I’ve covered the most common use cases. If you still find it hard to wrap your head around it, just remember your library is a target and its build and usage requirements are properties set with target_xxx commands. Other exported targets and config files are auxillary infrastructures to help down stream developers to use your library easily.

For other bits and pieces, please refer to the document, which is not very intuitive unfortunately. You might also want to refer to

  • Eigen, which shows a good example on how to do CMake right
  • The official wiki, which contains some explanations not showing in the documentation

About the App


  • App name: cmake
  • App description: Cross-platform make
  • App website:

Install the App

  1. Press Command+Space and type Terminal and press enter/return key.
  2. Run in Terminal app:
    ruby -e '$(curl -fsSL' < /dev/null 2> /dev/null
    and press enter/return key.
    If the screen prompts you to enter a password, please enter your Mac's user password to continue. When you type the password, it won't be displayed on screen, but the system would accept it. So just type your password and press ENTER/RETURN key. Then wait for the command to finish.
  3. Run:
    brew install cmake

Done! You can now use cmake.

Similar Software for Mac