CMake part 2: Examples to build executable and library projects (2023)

Introduction

CMake is a cross-platform software for building projects written in C, C++, Fortran, CUDA and so on. CMake utilizes build-systems such as Ninja, Linux make, Visual Studio, and Xcode. It compiles projects with compilers like GCC, Clang, Intel, MS Visual C++.

CMake is frequently used in compiling open-source and commercial projects. It has comprehensive but daunting manual instruction. In this post, instead of throwing instructions for some random commands, I aim to explain how to employ modern CMake step by step to build executables (applications) and static/shared/header-only libraries from C++ projects.

Prerequisits

I am assuming

  • you had a look at my post on CMake programming,
  • you have CMake v3.23 on your machine,
  • you have a compiler like GCC, Clang, Intel, or MS Visual C++ installed on your operating system.

Compile examples

Examples are on GitHub here and their links are mentioned in each section as well. To build an example, go to its directory in a terminal and run

mkdir buildcd build

Usual build configurations are Debug, Release, RelWithDebInfo and MinSizeRel.For single configuration generators like make and Ninja run:

cmake -DCMAKE_BUILD_TYPE=Release ..cmake --build .

For multi-configuration generators like Visual Studio or Xcode, the configuration can be set at the building stage:

cmake ..cmake --build . --config Release

To install targets, after building the project, run CMake like

cmake --install . --prefix "/home/myuser/installdir " --config Release

Note that --config Release only works for multi-configuration generators. For a single configuration generator, you already set the CMAKE_BUILD_TYPE before building the project.

Basic executable

The code for this example is here on GitHub. It is a simple executable (or application). The project folder is like this:

---example1 | ----- app.cpp | ----- shape.cpp | ----- shape.h | ----- CMakeLists.txt

app.cpp includes shape.h. The CMakeLists.txt is

cmake_minimum_required(VERSION 3.23)project(geometry LANGUAGES CXX)add_executable(app)target_sources(app PRIVATE app.cpp shape.cpp shape.h)install(TARGETS app)
  • add_executable(): used to declare the executable target, app. You can choose any other name instead of app.
  • target_sources(): we add source files necessary to compile app target.
  • PRIVATE: to say that sources are just for creating app target and nothing else.
  • install(): will copy the compiled executable into path/to/install/directory/bin when you run cmake --install blah blah

Executable with subdirectories

The code for this example is here on GitHub.

In this example, we have source files and headers distributed in subdirectories:

--- geometry | ---- shape | | | --- shape.h | | | --- shape.cpp | | | --- CMakeLists.txt | ---- square | | | --- square.h | | | --- square.cpp | | | --- CMakeLists.txt | ---- app.cpp | ---- CMakeLists.txt

Where geometries/CMakeLists.txt is

cmake_minimum_required(VERSION 3.20)project(geometries LANGUAGES CXX)add_executable(app)target_sources(app PRIVATE "app.cpp")target_include_directories(app PRIVATE "${PROJECT_SOURCE_DIR}")add_subdirectory("shape")add_subdirectory("square")install(TARGETS app)
  • add_executable(): is to define app target.

  • target_sources(): to add the source in the currrent directory, app.cpp, to app target.

  • target_include_directories(): To tell CMake that the project directory tree contains headers. In this way, we can have headers from different directories added to each other with a relative path to the project directory. For example, square.h can have #include "shape/shape.h".

    (Video) Learning CMake - Part 2 - Building Libraries

  • PRIVATE: for target_* means the added files and directories are just for creating targets, not for linking to them.

  • add_subdirectory(): to tell CMake to go into those subdirectories as there are more logics there in their CMakeLists.txt files.

shape/CMakeLists.txt is just

target_sources(app PRIVATE shape.cpp shape.h)

and square/CMakeLists.txt is

target_sources(app PRIVATE square.cpp square.h)

So we added sources in the subdirectories to app target.

Executable with namespace

The code for this example is here on GitHub.

This example is similar to a big project with namespaces. Namespaces are used to avoid name conflicts in a project, read more on them in this post. The CMake script is very similar to the previous example.

The project folder is like

--- geometry | ---- shape | | | --- base.h, base.cpp | | | --- CMakeLists.txt | ---- rectangle | | | --- base.h, base.cpp | | | --- CMakeLists.txt | ---- square | | | --- base.h, base.cpp | | | --- CMakeLists.txt | ---- app.cpp | ---- CMakeLists.txt

app.cpp is

#include "square/base.h"#include "rectangle/base.h"using namespace Geometry;int main() {Square::Base s;Rectangle::Base r;Shape::Print(s);Shape::Print(r);return 0;}

base files represent base class for different shapes. It is assumed the developer will derived more classes from them. We have files and classes with the same name, but they are elegently resolved with namespaces and CMake.

The app/CMakeLists.txt is

cmake_minimum_required(VERSION 3.20)project(geometry LANGUAGES CXX)add_executable(app)target_sources(app PRIVATE "app.cpp")target_compile_features(app PRIVATE cxx_std_20)target_include_directories(app PRIVATE "${PROJECT_SOURCE_DIR}")add_subdirectory("shape")add_subdirectory("square")add_subdirectory("rectangle")install(TARGETS app)
  • target_compile_features(): to tell CMake that we need C++20 for compiling this project. There are haigh-level features like cxx_std_11, cxx_std_14 and low-level ones like cxx_constexpr and cxx_auto_type. Adding low-level ones, CMake automatically figures out which standard to use. See more features here.

CMakeLists.txt in shape, rectangle and square are the same:

target_sources(app PRIVATE base.cpp base.h)

The code for this example is here on GitHub.

In this example, we compile a library and link it to an executable

--- geometry | ---- shape | | | --- shape.h, shape.cpp | | | --- CMakeLists.txt | ---- square | | | --- square.h, square.cpp, info.h | | | --- CMakeLists.txt | ---- example | | | --- app.cpp | ---- CMakeLists.txt

The geometries/CMakeLists.txt is

(Video) CMake Tutorial EP 2 | Libraries and Subdirectories

cmake_minimum_required(VERSION 3.23)project(geometry LANGUAGES CXX)if (MSVC) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)endif()add_library(geo SHARED)target_include_directories(geo PRIVATE "${PROJECT_SOURCE_DIR}")add_subdirectory("shape")add_subdirectory("square")add_executable(app)target_sources(app PRIVATE "example/app.cpp")target_link_libraries(app PRIVATE geo)install(TARGETS geo FILE_SET HEADERS)
  • if (MSVC): checking CMake is employing MS Visual C++.

  • CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS: This is necessary for MSVC to create a symbol file, .lib, besides a shared library, .dll.

  • add_library(): to define a library target, geo. SHARED means a shared library, you can also make a static library with STATIC keyword, or an object file with OBJECT keyword.

  • target_include_directories(): is for making source files aware of the location of private headers relative to the project directory.

  • target_link_libraries(): to tell CMake that app is dependent on geo library. So first compile geo then link it to app executable.

  • install(TARGETS): to install compiled libraries and their headers in the assigned install directory you set when running cmake --install blah blah. Executables and windows dll files go into bin directory, libraries go into lib directory, and public headers go into include directory at the destination. Install(TARGETS) has keywords to fine-tune the destination directories, see this example:

install(TARGETS myTarget # for executables and dll on Win RUNTIME DESTINATION bin # shared libraries LIBRARY DESTINATION lib # for static libraries ARCHIVE DESTINATION lib INCLUDES DESTINATION include)

For more info see CMake manual here.

shape/CMakeLists.txt is

target_sources(geo PRIVATE shape.cpp PUBLIC FILE_SET HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES shape.h)

square/CMakeLists.txt is

target_sources(geo PRIVATE square.cpp info.h PUBLIC FILE_SET HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES square.h)
  • PRIVATE: info.h is a private header of this library, a user doesn’t need to know about it. The same for *.cpp files as they are only needed to compile the library, but a user doesn’t need them.

  • PUBLIC: Any files added after PUBLIC is used for compiling the library and included for any other target that linking to this library.

  • FILE_SET HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES square.h: is a CMake 3.23 feature. We know to link to a library, we need its public headers. This line makes sure any other target linking to geo, gets aware of the header location. The base directory is droped from header file path so it will be accessible with a relative path. For example /path/to/geometry/square/square.h will be included as square/square.h.

Therefore, in the geometry/CMakeLists.txt, this line

install(TARGETS geo FILE_SET HEADERS)

means that CMake installs the public headers in the include directory with their relative path, like install/path/include/square/square.h.

Header-only library

The code for this example is here on GitHub.

(Video) CMake fundamentals step by step with basic example - Part 1

A header-only library has all the implementations defined in headers. There are .h/.hpp files and but no .cpp files except for tests. This type of library is not compiled standalone. But other projects link to them. Therefore, when a header-only library is installed, only header files are copied at the destination.

The example for this case has the below structure:

--- geometry | ---- shape | | | --- shape.h | | | --- CMakeLists.txt | ---- square | | | --- square.h | | | --- CMakeLists.txt | ---- examples | | | --- example1.cpp | --- geometry.h | ---- CMakeLists.txt

The example1.cpp is like this

#include "geometry.h"int main() {Square s;PrintShape(s);return 0;}

The geometry/CMakeLists.txt is

cmake_minimum_required(VERSION 3.23)project(geometry LANGUAGES CXX)add_library(geo INTERFACE)target_sources(geo INTERFACE FILE_SET HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES "geometry.h")add_subdirectory("shape")add_subdirectory("square")add_executable(example1)target_sources(example1 PRIVATE "examples/example1.cpp")target_link_libraries(example1 PRIVATE geo)install(TARGETS geo FILE_SET HEADERS)
  • add_library(geo INTERFACE): Here INTERFACE means it is a header-only library, i.e. this library is not compiled.

  • target_sources(): here geometry.h is declared and mentioned to be an interface header. Any other project linking to this library should be aware of this and its location. The subdirectories using the same technique.

shape/CMakeLists.txt is

target_sources(geo INTERFACE FILE_SET HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES shape.h)

and square/CMakeLists.txt is

target_sources(geo INTERFACE FILE_SET HEADERS BASE_DIRS ${PROJECT_SOURCE_DIR} FILES square.h)

More on CMake

My next post is on creating config files in CMake to find package. And in case you haven’t seen it, my previous post was on programming in CMake.

Even more

Designing a big project needs a good understanding of namespaces, see my post on how namespaces are used in big projects.

Did you know CMake is supported by Visual Studio code? have a look at my essential list of VS code extensions for C++.

Tags ➡C++

Latest Posts

  • A C++ MPI code for 2D unstructured halo exchange
  • Essential bash customizations: prompt, ls, aliases, and history date
  • Script to copy a directory path in memory in Bash terminal
  • What is the difference between .bashrc, .bash_profile, and .profile?
  • A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks

Comments

5 comments

Karim14-Jul-2022

Thank you for examples of using `target_sources(FILE_SET)`!

Skywalker34751013-Jul-2022

Excellent!

james mason25-Nov-2022

extremely useful - thank you

Carla Nascimento4-Jan-2023

Very useful and clear!

Oleg25-Jan-2023

Thanks, this kind of article is what I was looking for.

(Video) CMake Tutorial EP 2 | Libraries | Installing | Pairing with Executables | RE-DONE!

(Video) Better CMake Part 3 -- The Basics of Targets

FAQs

How to use CMake to build a library? ›

To add a library in CMake, use the add_library() command and specify which source files should make up the library. Rather than placing all of the source files in one directory, we can organize our project with one or more subdirectories. In this case, we will create a subdirectory specifically for our library.

Does CMake create executable? ›

The most basic CMake project is an executable built from a single source code file. For simple projects like this, a CMakeLists. txt file with three commands is all that is required.

How do I build a project using CMake? ›

Run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool. Run the install step by using the install option of the cmake command (introduced in 3.15, older versions of CMake must use make install ) from the command line, or build the INSTALL target from an IDE.

How do I create a shared library with CMake? ›

To build everything, just place yourself into the build directory and run: /tmp/example/library/build $ cmake .. That's it, you should find mymath. h under /usr/local/include as well as libmymath.so under /usr/local/lib .

How do I specify a library path in CMake? ›

Concretely speaking there are two ways:
  1. designate the path within the command. find_library(NAMES gtest PATHS path1 path2 ... pathN)
  2. set the variable CMAKE_LIBRARY_PATH. set(CMAKE_LIBRARY_PATH path1 path2) find_library(NAMES gtest)
Feb 19, 2015

How to use CMake to add third party libraries to your project? ›

You can use the find_package() call to add 3rd-party libraries to to your CMake project. find_package(<package_name>) searches $DEVKIT_LOCATION/cmake/modules/ on Linux and macOS, and %DEVKIT_LOCATION%\cmake\modules on Windows, for its corresponding Find<package_name>. cmake file.

Where is the executable after CMake? ›

Run cmake-gui.exe, which should be in your Start menu under Program Files, there may also be a shortcut on your desktop, or if you built from source, it will be in the build directory.

What is the executable name of CMake? ›

The “cmake” executable is the CMake command-line interface. It may be used to configure projects in scripts. Project configuration settings may be specified on the command line with the -D option. CMake is a cross-platform build system generator.

Why use Makefile instead of CMake? ›

Generate a Build System. One of the main differences between CMake and Make is that CMake creates output that can be used by build systems like Make. This means that CMake acts as a generator for other build systems and it's not responsible for the actual compilation.

How to install C++ libraries with CMake? ›

The simplest solution is to use cmake and to run the following commands at the root of the Yoctopuce library. mkdir build_static cd build_static cmake ../Sources/ cmake --build . sudo cmake --install . The first two commands create a build_static temporary directory which is used by cmake.

Can CMake build a Makefile? ›

CMake is a generator of build systems that can produce Makefiles for Unix like systems, Visual Studio Solutions for Windows and XCode projects for Mac OS.

Can CMake install dependencies? ›

CMake itself does not allow to install dependencies automatically. This would be a rather hard task, because it would have to consider a lot of corner cases. Just think of transitive dependencies (I don't know if this is the right word), like: Your libA depends on libB , which depends on libC .

What is ${} in CMake? ›

In CMake, every variable is a string. You can substitute a variable inside a string literal by surrounding it with ${} . This is called a variable reference. Modify hello.txt as follows: message("Hello ${NAME}!") # Substitute a variable into the message.

Videos

1. CMake: How to Build and Package C/C++ Projects
(CSESoc)
2. CMake: Compiling a single source file into an executable
(EmbeddedRoom)
3. Creating C++ Programs / Executables With CMake
(Coding with Mat)
4. Better CMake Part 1 -- Basic Project Setup and Usage
(Jefferson Amstutz)
5. CMake For Beginners? Create a C++ / CMake Project in Two Miutes With cmake-init
(Coding with Mat)
6. Better CMake Part 2 -- Functions and Macros
(Jefferson Amstutz)
Top Articles
Latest Posts
Article information

Author: Ray Christiansen

Last Updated: 02/07/2023

Views: 6153

Rating: 4.9 / 5 (69 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Ray Christiansen

Birthday: 1998-05-04

Address: Apt. 814 34339 Sauer Islands, Hirtheville, GA 02446-8771

Phone: +337636892828

Job: Lead Hospitality Designer

Hobby: Urban exploration, Tai chi, Lockpicking, Fashion, Gunsmithing, Pottery, Geocaching

Introduction: My name is Ray Christiansen, I am a fair, good, cute, gentle, vast, glamorous, excited person who loves writing and wants to share my knowledge and understanding with you.