Skip to content
Konstantin Soshin edited this page Apr 6, 2018 · 11 revisions

This page is maintained by Konstantin Soshin


About CMake

CMake is a cross-platform free and open-source software for managing the build process of software. It is used in conjunction with native build environments such as make, Apple's Xcode, and Microsoft Visual Studio. It has minimal dependencies, requiring only a C++ compiler on its own build system

Features

  • CMake can handle in-place and out-of-place builds, enabling several builds from the same source tree, and cross-compilation. The ability to build a directory tree outside the source tree is a key feature, ensuring that if a build directory is removed, the source files remain unaffected.

  • CMake can locate executables, files, and libraries. These locations are stored in a cache, which can then be tailored before generating the target build files.

  • CMake can generate project files for several prominent IDEs, such as Microsoft Visual Studio, Xcode, and Eclipse CDT. It can also produce build scripts for MSBuild or NMake on Windows; Unix Make on Unix-like platforms such as Linux, macOS, and Cygwin; and Ninja on both Windows and Unix-like platforms.

Basics of CMake

First iteration

For example you have src.cpp source file. First, you need to create a file for cmake, which is usually called CMakeLists.txt, and write this here:

   add_executable(myproject src.cpp)

add_executable adds an executable target to be built from the source files listed in the command invocation. That's enough to build simple projects.

Building the project

Simple variant

Type cmake CMakeLists.txt (you can also pass directory path to CMakeLists.txt by cmake argument cmake ./. or cmake ./path/to/my/cmake/ - see below). CMake will generate all necessary files and create Makefile in the current directory:

   default_user@default-user:~/myproject$ cmake CMakeLists.txt
   -- The C compiler identification is GNU 7.2.0
   -- The CXX compiler identification is GNU 7.2.0 
   -- Check for working C compiler: /usr/bin/cc
   -- Check for working C compiler: /usr/bin/cc -- works
   -- Detecting C compiler ABI info
   -- Detecting C compiler ABI info - done
   -- Detecting C compile features
   -- Detecting C compile features - done
   -- Check for working CXX compiler: /usr/bin/c++
   -- Check for working CXX compiler: /usr/bin/c++ -- works
   -- Detecting CXX compiler ABI info
   -- Detecting CXX compiler ABI info - done  
   -- Detecting CXX compile features
   -- Detecting CXX compile features - done
   -- Configuring done
   -- Generating done
   -- Build files have been written to: /home/myproject

Then just type make and everything is done!

   default_user@default-user:~/myproject$ make
   [ 50%] Building CXX object CMakeFiles/myproject.dir/src.cpp.o
   [100%] Linking CXX executable myproject
   [100%] Built target myproject

Briefly: cmake CMakeLists.txt, then make

Improved and more convenient variant

As it stated above, the main feature is that CMake can handle out-of-place builds. So it is quite convenient to have all build files and binaries away from current work directory in their own directory. We can make new build directory and launch CMake from there:

   default_user@default-user:~/myproject$ mkdir build && cd build
   default_user@default-user:~/myproject/build$ cmake ../.

Now binary myproject and all build files(CMakeCache.txt, cmake_install.cmake, etc) are in /build and aren't mixed with other source files.

Briefly: mkdir ./build && cd ./build, then cmake <path_to_CMakeLists.txt>, then make

Some remarks about rebuild

CMake designed in such way, that you may invoke it only once: when you first configure your project. After the first configuration the only command you ever need to run is your build command (make). It will detect changes in the build system, automatically regenerate all necessary files and compile the end result. The user does not need to care.

In other words, you shouldn't re-run CMake when you add new *.cpp, header, or have an intention to do some change inside.

Second iteration

Specifying the required version of cmake

   cmake_minimum_required(VERSION 2.6)

If the version of cmake is less than 2.6, it will not work. To write this command always is a good style

Project name

   project(Myproject)

Indicates that this cmake file is the root of some project. Stores the name in the PROJECT_NAME variable. Additionally this sets variables PROJECT_SOURCE_DIR, <PROJECT-NAME>_SOURCE_DIR, PROJECT_BINARY_DIR, <PROJECT-NAME>_BINARY_DIR(see next paragraph about variables).

Variables

In CMake you can create text variables. Command

   set(MYVAR The variable's value)

will write value The variable's value to variable MYVAR. To use the value somewhere, you need to write ${MYVAR}. To add a certain text to a variable, you can do this:

   set(MYVAR "${MYVAR} is 42")

Variables are actively used by various libraries - to set flags, build / link parameters (see paragraph below about libraries).

Quite beautiful variant of CMakeLists.txt with separate list of sources in vatiable:

   cmake_minimum_required(VERSION 2.6)
   set(SOURCES test.cpp lib1.cpp lib2.cpp)
   add_executable(test ${SOURCES})

Moreover, there are a lot of built-in variables, that provide information, change behavior, describe the system and control the build. There are some of them:

  • PROJECT_NAME - project name
  • CMAKE_CXX_FLAGS - compiler flags
  • CMAKE_LD_FLAGS - linker flags
  • CMAKE_BINARY_DIR - this is the full path to the top level of the current CMake build tree. For an in-source build, this would be the same as CMAKE_SOURCE_DIR.
  • CMAKE_SOURCE_DIR - the path to the top level of the source tree
  • CMAKE_RUNTIME_OUTPUT_DIRECTORY - where to put all the executable target files when built.
  • CMAKE_LIBRARY_OUTPUT_DIRECTORY - Where to put all the LIBRARY target files when built
  • CMAKE_ARCHIVE_OUTPUT_DIRECTORY - Where to put all the ARCHIVE target files when built.

See MOAR variables.

Including directories with headers

If you want your header-files (*.h) to be searched in special directories, you can list it in include_directories:

   include_directories("headers/myheaders" "/usr/include" "blahblah")

Very important: searching and including libraries

Let's learn how to search and connect libraries with cmake using Boost as an example

   set(Boost_USE_STATIC_LIBS OFF)
   set(Boost_USE_MULTITHREADED ON)

Firstly, we want this library be linked staticly. Second flag allows Boost use multithreading. Suppose we need the components of boost called chrono (a library for working with time) and filesystem (a library for working with the file system):

   find_package(Boost COMPONENTS chrono filesystem REQUIRED)

REQUIRED indicates that the library is necessary for the project. Then we will have automatically defined these variables: Boost_INCLUDE_DIRS - path to headers connected with necessary components, Boost_LIBRARIES - path to binary library. Then we include directories with headers:

   include_directories(${Boost_INCLUDE_DIRS})

and use special comand target_link_libraries to link library:

   target_link_libraries(Myproject ${Boost_LIBRARIES})

Notice: not all libraries can be linked in that way. Only those which have Find%libraryname%.cmake could be found using find_package. For example, libelf doesn't have. But in that case plan is the same: find path to headers and path to binary library and then include_directories and target_link_libraries. How to find these paths see: find_path, find_library.

How make a library in a subdirectory and link it with the main program

For example, in ./ we have main.cpp, in ./mymath we have mymath.h where our math functions are declared and mymath1.cpp, mymath2.cpp, mymath3.cpp where they are realized. So, how should we build that project? The first solution seems obvious: put all sources to add_executable and that's all.

./CMakeLists.txt:

   # First variant
   cmake_minimum_required (VERSION 2.6)
   project (calc)

   set(SRC main.cpp mymath/mymath1.cpp mymath/mymath2.cpp mymath/mymath3.cpp)
   set(HEADERS mymath/mymath.h)

   add_executable(cartcalc ${SRC})
   include_directories(${HEADERS})

But it is more convenient to organize our math files to one library using add_library and then just link it using target_link_library:

./CMakeLists.txt

   # Second variant
   cmake_minimum_required (VERSION 2.6)
   project (calc)

   set(SRC mymath/mymath1.cpp mymath/mymath2.cpp mymath/mymath3.cpp)
   set(HEADERS mymath/mymath.h)
   
   add_library(math STATIC ${SRC})

   add_executable(calc main.cpp)
   target_link_library(calc math)

   include_directories(${HEADERS})

Another and the most well-styled variant is to have two CMakeLists.txt: the first is for our math library, and the second is the main CMakeLists.txt for the whole project, which will call the first one using add_subdirectory:

./CMakeLists.txt

   #Third variant
   cmake_minimum_required (VERSION 2.6)
   project (calc)

   set(SRC main.cpp)
   set(HEADERS mymath/mymath.h)

   add_executable(calc ${SRC})

   add_subdirectory(mymath)
   target_link_library(calc math)

   include_directories(${HEADERS})

./mymath/CMakeLists.txt

   cmake_minimum_required (VERSION 2.6)
   project (math)

   set(SRC mymath1.cpp mymath2.cpp mymath3.cpp)
   set(HEADERS mymath/mymath.h)

   add_library(math STATIC ${SRC})

Defining macroses for compiler (-D flag)

add_definitions(-DSOME_IMPORTANT_DEFINITION) this command adds -D define flags to the compilation of source files. It is intended to add preprocessor definitions.

We need to go deeper

Сonditions, cycles, and other features

if(), else(), elseif()

Conditionally execute a group of commands. Typical usage:

   if(expression1)
     COMMAND1(...)
     COMMAND2(...)
   elseif(expression2)
     COMMAND3(...)
     COMMAND4(...)
   else(expression3)
     COMMAND5(...)
     COMMAND6(...)
   endif()

Possible expressions are:

  • if(<constant>)
  • if(<variable>)
  • if(NOT <expr>)
  • if(<expr1> OR/AND <expr2>)

Learn more in documentation

list()

It is quite easy to handle a lot of objects (for example source files) organizing them into lists. list provide operations with them.

foreach()

It is a good way to process a lot of objects (for example source files). foreach() evaluates a group of commands for each value in a list.

string()

Sometimes you need to do some operations with strings. For example you want to transform source file's name to object file's, our to replace some part(s) of filename. string() may help you.

Example

CMakeLists.txt in our project can serve as an example of usage foreach and string.

   # Here TESTS contains all source directories with unit tests
   foreach(ITER IN LISTS TESTS) # cycle begins
       
       # each name from TESTS is concatinates with string "/t/*.*"
       string(CONCAT GLOBBING_EXPR ${ITER} "/t/*.*")
       
       # see below about file manupulations. Here in INPFILES we get all files from the current directory from 
       # TESTS
       file(GLOB INPFILES LIST_DIRECTORIES false ${GLOBBING_EXPR})


       foreach(ITER2 IN LISTS INPFILES)
           # copy each name from INPFILES to directory where building is happening now
           file(COPY ${ITER2} DESTINATION ./.)
       endforeach()
       
       # each name from TESTS concatinates with the string "/t/unit_test.cpp"
       string(CONCAT SRC_UNIT_TEST ${ITER} "/t/unit_test.cpp")

       # replace all "/" to "_" in each filename
       string(REGEX REPLACE "/" "_"  EXEC_NAME_NOTFULL ${ITER})

       # another concatination
       string(CONCAT EXEC_NAME ${EXEC_NAME_NOTFULL} "_test")
    
       add_executable(${EXEC_NAME} ${SRC_UNIT_TEST})
       target_link_libraries(${EXEC_NAME} gtest mipt-mips-src ${Boost_LIBRARIES} ${LIBELF_LIBRARIES})
       add_test(NAME ${EXEC_NAME} COMMAND ${EXEC_NAME})

   endforeach()

File manipulations

Someties it is really necessary to work with files (searching, copying, creating, reading and writing them). Copying, creating, etc are provided by function file(see example above). For searching files there are some specialized functions:find_file, find_library, find_path, find_program

CTest

CMake has support for adding tests to a project:

enable_testing()

This adds another build target, which is test for Makefile generators, or RUN_TESTS for integrated development environments (like Visual Studio). From that point on, you can use the ADD_TEST command to add tests to the project:

add_test( testname Exename arg1 arg2 ... )

Once you have built the project, you can execute all tests via

make test

with Makefile generators, or by rebuilding the RUN_TESTS target in your IDE. Internally this runs CTest to actually perform the testing; you could just as well execute

ctest

in the binary directory of your build.

Further Reading

Clone this wiki locally