CMake is a popular cross-platform build system that allows developers to use a common interface to define a set of rules to build the source code with different compilers, such as GCC, Clang and Visual Studio. In fact, CMake supports not only C/C++, but also other languages such as C# and Fortran. CMake is an essential tool for building software projects and making the whole process easier for anyone.
I put my code here.
Basic
How to start
After you install CMake, the starting point is to create a CMakeLists.txt
file. Three basic commands are required for the minimum build process. Below is a basic CMakeLists.txt
file template:
cmake_minimum_required(VERSION 3.12) |
Don’t forget to create a source file named main.cpp
alongside with the CMakeLists.txt
file:
|
To build the project, open the terminal where you create your CMakeList.txt
file, create a new folder mkdir build && cd build
and just type cmake ..
. CMake would output a bunch of files for building the system, the last step is to actually compile&link the project. Depending on the underlying generators, we could build the project with make
or ninja
or a more general command cmake --build . --config Release
. The default behavior is to generate an executable object with release configs on the current folder. Type ./helloworld
to run the program.
CMake uses the jargon target refering to those final executable objects or libraries, e.g. helloworld
in this demo. LANGUAGES C CXX
specifies the programming languages needed to build the project. CMAKE_CXX_STANDARD
specifies the c++ standard to build the project.
How to find headers
So far we have only compiled one source file, but what if we have hundreds of source files and header files scattered across multiple directories? I created a folder structure looks like this:
includes/ |
To compile this project, we tell CMake that we also need to compile algo.cpp
and algo.h
:
cmake_minimum_required(VERSION 3.12) |
target_include_directories
refers to where to seach for additional headers, the includes
directory in this case. And PRIVATE
refers to that these header files are only visible for the target helloworld
, not for any target linking against the target defined here (if helloworld
is a library). include_directories
acts as the same purpose but for all targets in the current CMakeLists.
How to collect all sources
Instead of manual settings, an easy way to get all cpp files is to use file
command:
cmake_minimum_required(VERSION 3.12) |
which collects files under the current and utils
directories with the postfix .cpp
and stores into the variable SOURCES
.
Another way to collect all source files is to use command aux_source_directory
like this:
cmake_minimum_required(VERSION 3.12) |
How to add definitions
It’s normal to add preprocessor definitions to control the compiling process. Before version 3.12, CMake used command add_definitions
which affects all targets in the current directory and subdirectories, even if you don’t want to. In version 3.12, CMake introduced a new command add_compile_definitions
which is more specific to targets in the current CMakeLists. It’s generally recommended to move towards to the new command.
The following code defines EXPORT_DLL
:
add_definitions(-DEXPORT_DLL) |
It’s also possible to add definitions to a specific target:
target_compile_definitions(helloworld PRIVATE EXPORT_DLL) |
How to pass compiler flags
Sometimes we want to control the compiler behaviors more precisely. CMake has add_compile_options
for all targets in the current directory and subdirectories, and targe_compile_options
for a specific target.
The following code tells gcc/g++ to use O3
optimization level and generate debug information to be used by GDB:
add_compile_options(-O3) |
How to designate output folders
There are some predefined variables to specifies the output directories:
CMAKE_RUNTIME_OUTPUT_DIRECTORY
refers to where to put executable files (.exe
) created byadd_executable
or.dll
created byadd_library
withSHARED
on Windows platformCMAKE_LIBRARY_OUTPUT_DIRECTORY
refers to where to put shared libraries (.so
) created byadd_library
withSHARED
option on Linux platformCMAKE_ARCHIVE_OUTPUT_DIRECTORY
refers to where to put static libraries (.a
,.lib
) created byadd_library
withSTATIC
option or.lib
created byadd_library
withSHARED
option on Windows platform
CMake also has a few useful variables to specify directories:
CMAKE_SOURCE_DIR
contains the directory where the calledCMakeLists
existsCMAKE_BINARY_DIR
contains the directory where you generate those temporary files to build the project
For our helloworld project, the executable helloworld
would be put under the lib
folder:
cmake_minimum_required(VERSION 3.12) |
How to compile static libraries
Just use add_library
instead of add_executable
:
cmake_minimum_required(VERSION 3.12) |
This CMakeLists builds a static library libstalib.a
.
How to compile shared libraries
I also made a new folder dynlib
under my helloword
project. CMake compiles shared libraries by add_library
command with SHARED
option:
cmake_minimum_required(VERSION 3.12) |
The above config generates libdynlib.so
, which can be linked into a larger program.
By default, Linux exports all symbols when compiles a shared library. However, Windows does the opposite thing. To control the visibility of symbols on Linux, we could add -fvisibility=hidden -fvisibility-inlines-hidden
to the compiler to make sure all symbols unvisible, until we make it explicitly by adding __attribute__((visibility('default')))
before symbols (Windows uses __declspec(dllexport)
and __declspec(dllimport)
). This would greatly reduce the chance of symbol collision. For compatible code, we could use macro definitions:
|
Use command nm -C -D libdynlib.so
to list all exported symbols:
w _ITM_deregisterTMCloneTable |
we can see that void hello(int i)
is not exported.
How to link libraries
Use target_link_libraries
to link static and shared libraries for a single target:
cmake_minimum_required(VERSION 3.12) |
or link_libraries
which affects all targets created later in the current directory and subdirectories.
How to add library seaching paths
It’s also common to add library seaching paths. CMake uses link_directories
and target_link_directories
(available in version 3.13) to add library paths, for targets created later and a single target, respectively.
cmake_minimum_required(VERSION 3.12) |
These library searching paths can be found in a preset variable CMAKE_LIBRARY_PATH
.
How to compile multiple targets on a single CMakeLists
add_subdirectory
searches available CMakeLists on the subdirectory so that it can be compiled on the top-level called CMakeLists:
cmake_minimum_required(VERSION 3.13) |
This CMakeLists firstly builds two libraries and then build the executable object helloworld
, which links these libraries.
How to compile a CUDA program
CMake automatically triggers nvcc
for compiling files with the .cu
extension. I would compile a cuda kernel funtion into dynlib
and call it in the main program. To do that, I created a new dyncuda.cu
file:
|
and its header looks like:
__global__ void cudahello(); |
In this way, the cuda kernel function would be launched by a cpp wrapper function exported to other programs. CMakeLists compiling the CUDA program looks like this:
cmake_minimum_required(VERSION 3.13) |
which specifies CUDA architectures with CMAKE_CUDA_ARCHITECTURES
. CMake would automatically search for cuda headers and libraries if they are installed on system paths. An old way to trigger CUDA compiling behavior is to use find_package(CUDA)
.