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 --versionbash
CMakeLists.txt
The rules to generate makefile.
- commands are not case-sensitive (e.g.,
project()orPROJECT()) for historical reason. - variables are case-sensitive! (e.g., must be
PROJECT_NAME) - paths can be string or raw (e.g.,
includeor"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"bash
Build with cmake
Traditional way:
mkdir build
cd build
cmake ..
make -j8
./binarybash
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 typebash
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
)CMake
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
)cmake
what is a
targetintarget_*(): (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
)
cmake
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"bash
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 executablecmake
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
)cmake
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()cmake
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()cmake
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
We can also set it with command line:
cmake .. -DCMAKE_CXX_FLAGS="-fPIC"bash
Set per-target c++ flags:
target_compile_definitions(test PUBLIC
fPIC
)cmake
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}cmake
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 ./buildbash
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"bash
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
)cmake
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.hare in the same dir as.cpp), then the include dirs.
- If you use CMakelists.txt to manage the include directories,