CMake Notes #1

A simple demo

A simple demo of what CMake does:

│  CMakeLists.txt
│  main.cpp

CMakeLists.txt:

# CMake is not case-sensitive.

# Require a minimum version of cmake.
cmake_minimum_required(VERSION 3.1)

# Set the name of the project.
project(demo)

# Add an executable to the project using the specified source files.
ADD_EXECUTABLE(hello ${DIR_SRCS})

main.cpp:

#include <stdio.h>

int main()
{
    printf("hello cmake");
    return 0;
}

What CMake does is to select a generator that generates the project based on these files above. For example, if a “Visual Studio 15 2017” generator is selected, CMake would first create a Visual Studio solution (.sln file and other auxiliary resources), then compile it into an executable file (hello.exe) with compiler provided by Visual Studio.

Run the following commands:

Or we can use CMake GUI to build the project with separated “configure” and “generate” functions. In the configure stage, it looks up all CMakeList.txt files and builds up an internal representation of the project. Then it generates the project files guided by the representation. In command line mode, cmake command combines the two stages.

Multiple source files within the same directory

│  CMakeLists.txt
│  main.cpp
│  foo.h
│  foo.cpp
# CMakeFiles.txt


# ----------------------- version 1 -----------------------

cmake_minimum_required (VERSION 3.1)

project(demo)

ADD_EXECUTABLE(hello main.cpp foo.cpp)


# ----------------------- version 2 -----------------------

cmake_minimum_required (VERSION 3.1)

project(demo)

# look up all source files in the directory (main.cpp, math.cpp)
# and save them in variable DIR_SRCS
aux_source_directory(. DIR_SRCS)

ADD_EXECUTABLE(hello ${DIR_SRCS})
// ----------------------- main.cpp -----------------------

#include "foo.h"

int main()
{
    doFoo();
    return 0;
}

// ----------------------- foo.h -----------------------
#ifndef FOO
#define FOO

void doFoo();

#endif

// ----------------------- foo.cpp -----------------------
#include<iostream>

void doFoo() {
    std::cout << "foo!\n";
}

Multiple source files within multiple directories

│  CMakeLists.txt
│  main.cpp
│
└─foo_stuff
        CMakeList.txt
        foo.cpp
        foo.h

All sub-directories must contain its own CMakeList.txt file.

# -----------------------  ./CMakeLists.txt -----------------------

cmake_minimum_required (VERSION 3.1)

project(demo)

aux_source_directory(. DIR_SRCS)

add_executable(hello ${DIR_SRCS})

# add subdirectory (for CMake to look up CMakeFiles.txt)
add_subdirectory(foo_stuff)

# next we need to add link library "FooLib"
# to the target "hello". The library FooLib is
# defined in the other CMakeLists.txt file
target_link_libraries(hello FooLib)

# -----------------------  ./foo_stuff/CMakeLists.txt -----------------------
aux_source_directory(. DIR_LIB_SRCS)

# a library instead of exe
add_library (FooLib ${DIR_LIB_SRCS})
// main.cpp

#include "foo_stuff/foo.h"

The generated VS solution should be something like this:

However, the library FooLib is not included in the “Additional Include Directories” of project “hello”, so foo.h must be referenced following the directory tree of the source code.

To include the path of the library:

cmake_minimum_required (VERSION 3.1)

project(demo)

aux_source_directory(. DIR_SRCS)

add_executable(hello ${DIR_SRCS})

# add include directories to the build.
include_directories("${PROJECT_SOURCE_DIR}/foo_stuff")
add_subdirectory(foo_stuff)

target_link_libraries(hello FooLib)

Now the “Additional Include Directories” of project “hello” will look like this:

And in main.cpp the library could be included without the relative path:

// main.cpp

#include "foo.h"

Custom compile options

│  CMakeLists.txt
│  config.h.in
│  main.cpp
├─foo_1
│      CMakeLists.txt
│      foo.cpp
│      foo.h
│
└─foo_2
        CMakeLists.txt
        foo.cpp
        foo.h

Here, two FooLib libraries are provided in foo_1 and foo_2 respectively, both containing its foo.h. If we want users to customize which FooLib to be compiled into the project, we can add options to CMake.

cmake_minimum_required (VERSION 3.1)

project(demo)

# Copy a file to another location and modify its contents.
configure_file(
    "${PROJECT_SOURCE_DIR}/config.h.in"
    "${PROJECT_SOURCE_DIR}/config.h"
)

# Provide an option that the user can optionally select
# Provides an option for the user to select as ``ON`` or ``OFF``
option(USE_FOO_1 "Use foo_1" ON)

if(USE_FOO_1)
    include_directories("${PROJECT_SOURCE_DIR}/foo_1")
    add_subdirectory(foo_1)
else()
    include_directories("${PROJECT_SOURCE_DIR}/foo_2")
    add_subdirectory(foo_2)
endif()

aux_source_directory(. DIR_SRCS)

add_executable(hello ${DIR_SRCS})

target_link_libraries(hello FooLib)
// config.h.in

#cmakedefine USE_FOO_1

After clicking “configure” in CMake, an extra option USE_FOO_1 is shown in the option list, which user can check/uncheck it to modify which FooLib to use.

If the option is checked, a config.h file will be generated that looks like this:

#define USE_FOO_1

Otherwise, it looks like this:

/* #undef USE_FOO_1 */

Configure File

https://cmake.org/cmake/help/latest/command/configure_file.html

The configure_file command copies the content of config file, set the options, and replace variables (@var@ or ${var}) with their respective values.

Example:

// config.h.in
#cmakedefine CMAKEDEFINE_TEST
#define NORMAL_TEST @normal_test@
#define CACHE_ENTRY_TEST @cache_entry_test@
# cmake
option(CMAKEDEFINE_TEST "cmakedefine test" ON)
set(normal_test 123)
set(cache_entry_test "stuff" CACHE STRING "This is a test")
set(ENV{PATH} "/home/path/to")

In CMake GUI:

config.h output:

#define CMAKEDEFINE_TEST
#define NORMAL_TEST 123
#define CACHE_ENTRY_STRING_TEST stuff
#define CACHE_ENTRY_PATH_TEST /home

Set

3 types of set commands:

https://cmake.org/cmake/help/latest/command/set.html


https://stackoverflow.com/questions/39401003/why-there-are-two-buttons-in-gui-configure-and-generate-when-cli-does-all-in-one

https://www.hahack.com/codes/cmake/