-
Notifications
You must be signed in to change notification settings - Fork 139
CMake
This page is maintained by Konstantin Soshin
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
-
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.
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.
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
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
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.
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(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).
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 asCMAKE_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 theLIBRARY
target files when built -
CMAKE_ARCHIVE_OUTPUT_DIRECTORY
- Where to put all the ARCHIVE target files when built.
See MOAR variables.
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")
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
.
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})
add_definitions(-DSOME_IMPORTANT_DEFINITION)
this command adds -D define flags to the compilation of source files. It is intended to add preprocessor definitions.
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
It is quite easy to handle a lot of objects (for example source files) organizing them into lists. list
provide operations with them.
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.
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.
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()
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
CMake has support for adding tests to a project:
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.
MIPT-V / MIPT-MIPS — Cycle-accurate pre-silicon simulation.