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 ofapp
.target_sources()
: we add source files necessary to compileapp
target.PRIVATE
: to say that sources are just for creatingapp
target and nothing else.install()
: will copy the compiled executable intopath/to/install/directory/bin
when you runcmake --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 defineapp
target.target_sources()
: to add the source in the currrent directory,app.cpp
, toapp
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 LibrariesPRIVATE
: fortarget_*
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 theirCMakeLists.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 likecxx_std_11
,cxx_std_14
and low-level ones likecxx_constexpr
andcxx_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
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 withSTATIC
keyword, or an object file withOBJECT
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 thatapp
is dependent ongeo
library. So first compilegeo
then link it toapp
executable.install(TARGETS)
: to install compiled libraries and their headers in the assigned install directory you set when runningcmake --install blah blah
. Executables and windows dll files go intobin
directory, libraries go intolib
directory, and public headers go intoinclude
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 afterPUBLIC
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 togeo
, 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 assquare/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.
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()
: heregeometry.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.
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? ›- designate the path within the command. find_library(NAMES gtest PATHS path1 path2 ... pathN)
- set the variable CMAKE_LIBRARY_PATH. set(CMAKE_LIBRARY_PATH path1 path2) find_library(NAMES gtest)
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.