Cmake
cmake
To make cross-platform compilation less painful (or maybe more painful ?)
CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice.
# check version
cmake --version
CMakeLists.txt
The rules to generate makefile.
* commands are not case-sensitive (e.g., project()
or PROJECT()
) for historical reason.
* variables are case-sensitive! (e.g., must be PROJECT_NAME
)
* paths can be string or raw (e.g., include
or "include"
)
* command parameters are separated by spaces or line breaks.
Project structure
A regular C/C++/CUDA project layout:
Readme.md
CMakeLists.txt
./include
mylib.h
./src
mylib.cpp
main.cpp # it #include "mylib.h"
Build with cmake
Traditional way:
mkdir build
cd build
cmake ..
make -j8
./binary
More modern way:
cmake -B build . # mkdir build && cd build && cmake ..
cmake --build build -j 8 # invoke make
./build/binary
cmake --build build --config Release -j 8 # set build type
Built in variables
Like shell variables, use with ${CAPITAL_NAME}
PROJECT_NAME # project name defined in project()
PROJECT_SOURCE_DIR # the source dir, usually ./
PROJECT_BINARY_DIR # the target dir, usually ./build/
# define your variables
set(SOURCES
src/main.cpp
src/test.cpp
)
Basic example
To include mylib
, we can use target_include_directories
for our target:
cmake_minimum_required(VERSION 3.5)
project(test_example) # project name
add_executable(test # output binary name
# all the sources
src/main.cpp
src/mylib.cpp
)
target_include_directories(test PUBLIC # target + qualifier (use public as default)
include # dir to include
)
what is a
target
intarget_*()
: (1) executable byadd_executable()
(2) library byadd_library()
, sometimes we need to link library to library.
We can also use global include_directories
if these headers are globally needed:
cmake_minimum_required(VERSION 3.5)
project(test_example) # project name
include_directories(include)
add_executable(test # output binary name
# all the sources
src/main.cpp
src/mylib.cpp
)
include_directories()
equals to the -I
option in gcc
, denoting the folders to search for headers.
It supports nested folders, e.g., the following file structure will still work:
./include
./mylib
mylib.h
./src
main.cpp # change to #include "mylib/mylib.h"
We can also (1) create a static library (2) link it to the executable:
cmake_minimum_required(VERSION 3.5)
project(test_example) # project name
include_directories(include)
add_library(mylib STATIC # build a static lib, called libmylib.a on linux, or libmylib.lib on windows
# move all the sources here
src/mylib.cpp
)
add_executable(test src/main.cpp) # only add the main.cpp
target_link_libraries(test PUBLIC mylib) # link mylib to the executable
We can also build a dynamic/shared library by:
add_library(mylib SHARED # named libmylib.so on linux , or libmylib.dll on windows
src/mylib.cpp
)
Difference between static and dynamic libraries: (1) static lib will be copied to the binary, leading to a much larger binary size (both in storage and memory cost), but no runtime external dependency. (2) dynamic lib is only referenced by the binary, and called in runtime as an external dependency (the dynamic loading from OS will lead to a small additional time cost too). (3) In most cases, dynamic lib is preferred.
For Windows MSVC, we need to add additional things for shared lib to work:
if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
set(BUILD_SHARED_LIBS TRUE)
endif()
Configurations
Build types
Built-in build types and their equal command:
* Release: -O3 -DNDEBUG
* Debug: -g
* RelWithDebInfo: -O2 -g -DNDEBUG
,
Explicitly use it by cmake .. -DCMAKE_BUILD_TYPE=Release
.
We can also detect and set default build type:
# Set a default configuration if none was specified
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "No release type specified. Setting to 'Release'.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo")
endif()
Compile flags
Set default (global) c++ flags:
# set c++ compile flags
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") # usually this is enough
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" CACHE STRING "doc string" FORCE) # force set in CMakeCache.txt... seems not very needed.
# set c++ standard
set(CMAKE_CXX_STANDARD 14)
cmake .. -DCMAKE_CXX_FLAGS="-fPIC"
Set per-target c++ flags:
target_compile_definitions(test PUBLIC
fPIC
)
Thrid party dependency
find_package()
can handle about 142 common third party libraries, to automatically handle it for your project.
# find and include boost 1.46.1; error out if not found; only find the following components
find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)
# check if the package is found
if(Boost_FOUND)
message ("Boost found")
include_directories(${Boost_INCLUDE_DIRS}) # include headers.
else()
message (FATAL_ERROR "Cannot find Boost")
endif()
# your binary
add_executable(test src/main.cpp)
# link library
target_link_libraries(test PRIVATE Boost::filesystem) # alias target, equals to ${Boost_FILESYSTEM_LIBRARY}
Build with ninja
what is
ninja
? A small build system (i.e., alternative tomake
) focusing on speed.
Cmake can generate ninja config too:
cmake -B build -G Ninja . # generate ./build/build.ninja
ninja -C build # build in ./build
Sub-projects
Let say your projects include some sub-projects that contain individual CMakeLists.txt
:
Readme.md
CMakeLists.txt
# these dependencies are sub-projedts
./dependencies
./package1 # a normal package
CMakeLists.txt
./include
lib1.h
./src
lib1.cpp
./package2 # a header-only package
CMakeLists.txt
./include
lib2.h
...
./include
mylib.h
./src
mylib.cpp
main.cpp # it #include "mylib.h", #include "package1/lib1.h"
To use these packages in your binary, use:
cmake_minimum_required(VERSION 3.5)
project(test_example) # project name
include_directories(include)
# regular dependency: first process their CMakeLists.txt
add_subdirectory(dependencies/package1)
# header-only dependency: just include it
include_directories(dependencies/package2)
add_library(mylib STATIC # build a static lib, called libmylib.a on linux, or libmylib.lib on windows
# move all the sources here
src/mylib.cpp
)
add_executable(test src/main.cpp) # only add the main.cpp
target_link_libraries(test PUBLIC
package1 # link package1
mylib
)
Misc
- Should I use
#include <mylib/mylib.h>
or#include "mylib/mylib.h"
? * If you use CMakelists.txt to manage the include directories,<>
is the only thing you need. *<>
only searches the include dirs, while""
searches the current directory first (e.g., when.h
are in the same dir as.cpp
), then the include dirs.