project

The project() command is used to define a new CMake project. In this example, it creates a project named mini_trader and specifies that the project uses C++ via the CXX language.

By declaring LANGUAGES CXX, CMake knows that it needs to locate and configure a suitable C++ compiler (such as g++ or clang++). This step also initializes important variables and toolchain settings required for building C++ targets later in the project.

While project() can support multiple languages, explicitly specifying only the languages you need helps keep configuration faster and more predictable.

1project(mini_trader LANGUAGES CXX)

option

The option() command in CMake defines a boolean configuration flag (ON/OFF) that users can toggle when configuring a project. It creates a cache variable, meaning the value is stored and can be modified through the command line or CMake GUI tools.

1option(<variable> "<help_text>" [value])

The optional [value] sets the default state of the option. If not provided, it defaults to OFF.

1option(ENABLE_TSAN  "Enable ThreadSanitizer" OFF)
2option(ENABLE_ASAN  "Enable AddressSanitizer" OFF)
3option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)

Users can override these defaults at configuration time using the -D flag:

1mkdir build
2cd build
3cmake -DENABLE_TSAN=ON ..
4make

Options are commonly used to control optional features such as enabling tests, documentation, or debug tooling. Since they are stored in CMakeCache.txt, their values persist across multiple CMake runs until explicitly changed.

You can use these variables in conditional logic to modify build behavior:

1set(SANITIZER_FLAGS)
2
3if (ENABLE_TSAN)
4    message(STATUS "ThreadSanitizer enabled")
5    list(APPEND SANITIZER_FLAGS
6        -fsanitize=thread
7        -g
8        -O1
9    )
10endif()

In CMake, a list is a semicolon-separated sequence of values. The set() command can be used to create or initialize lists. For example:

1set(var a b c)      # a;b;c
2set(var "a b c")  # single string element

In this example,SANITIZER_FLAGS is a list used to collect compiler flags. When ENABLE_TSAN is enabled, the following flags are appended:-fsanitize=thread enables ThreadSanitizer to detect data races and unsafe concurrent access,-g includes debug symbols for readable stack traces, and -O1 applies light optimization (higher levels such as O3 may interfere with sanitizer accuracy).

add_subdirectory

add_subdirectory tells CMake to enter another folder, read its CMakeLists.txt, and treat everything defined there as part of the same build.

1add_subdirectory(tests)

When CMake processes this line, it immediately steps into the tests/ directory, loads tests/CMakeLists.txt, and executes it just as if its contents were written in the main file.

Any targets created inside that subdirectory, such as executables or libraries defined with add_executable or add_library, become part of the same overall project. These targets can be built with make, linked against other targets, and discovered and run by ctest.

The big picture in modern CMake is that everything revolves around targets. A target represents a concrete buildable unit, such as an executable or a library, and CMake manages dependencies and relationships between these targets automatically.

add_executable

The add_executable() command defines an executable target from a set of source files. It is one of the core building blocks in CMake, responsible for telling the build system how to produce a runnable binary.

Only source files (such as .cppor .c) need to be listed. Header files do not need to be explicitly included, as modern compilers automatically track dependencies through #include directives.

1add_library(db_core
2    src/buffer_pool.cpp
3    src/disk_manager.cpp
4    src/page.cpp
5    src/lru_replacer.cpp
6)
7
8add_executable(databasecpp
9    src/main.cpp
10)
11
12target_link_libraries(databasecpp PRIVATE db_core)

In this example,add_library() is used to group related source files into a reusable module called db_core. This allows the core database logic to be developed and maintained independently of the final executable.

The add_executable() command then defines the final application databasecpp, using main.cpp as its entry point.

Finally,target_link_libraries() links the executable with the db_core library. The PRIVATE keyword means that this dependency is only required for building the executable itself and is not propagated to other targets.

Structuring projects this way promotes modular design, making code easier to reuse, test, and maintain instead of placing all implementation directly inside a single executable target.

target_include_directories

target_include_directories controls where the compiler looks for header files when it encounters an #include directive.

For example, when the compiler sees an include like the following, it needs to know which directories to search in order to find the corresponding header files.

1#include "order_book.hpp"
2#include <boost/asio.hpp>

The compiler first checks the directory of the current source file. If the header is not found there, it then searches through the include directories provided by target_include_directories.

The general syntax of this command is shown below. The visibility keyword determines how the include directories are applied to the target and any targets that depend on it.

1target_include_directories(<target>
2    PRIVATE | PUBLIC | INTERFACE
3    <dir1> <dir2> ...
4)

Using PRIVATE means the include directories are only used when compiling this target. PUBLIC applies the directories to this target and anything that links against it, while INTERFACE applies them only to dependent targets.

In the example below, the Boost include directory is added so the compiler can find Boost headers such as boost/asio.hpp .

1set(BOOST_INCLUDEDIR "/usr/local/opt/boost/include")
2
3add_executable(
4  mini_trader
5  src/main.cpp
6  src/order_book.cpp
7  src/tcp_server.cpp
8)
9
10# Important: all source files must be listed so they are compiled and linked
11target_include_directories(
12  mini_trader
13  PRIVATE ${Boost_INCLUDE_DIRS}
14)

target_compile_options vs target_link_options

The target_compile_options() and target_link_options() commands are used to apply flags to a specific target in CMake. They control different stages of the build process: compilation and linking.

target_compile_options() applies flags during the compilation step, when source files are compiled into object files. This is where you typically add flags such as warnings, optimizations, or sanitizer instrumentation.

target_link_options() applies flags during the linking step, when object files are combined into the final executable or library. Some features, such as sanitizers, require flags to be present at both compile time and link time to work correctly.

1if (SANITIZER_FLAGS)
2    target_compile_options(databasecpp PUBLIC ${SANITIZER_FLAGS})
3    target_link_options(databasecpp PUBLIC ${SANITIZER_FLAGS})
4endif()

In this example, the same set of sanitizer flags is applied to both stages. This ensures that instrumentation is added during compilation and that the required runtime libraries are correctly linked.

The PUBLIC keyword means that these options will also propagate to targets that depend on databasecpp. In practice, compile and link options are often marked as PRIVATE unless you explicitly want dependent targets to inherit them.

Using target-specific commands like these is preferred over global variables (such as CMAKE_CXX_FLAGS) because it keeps configuration modular, predictable, and easier to maintain as your project grows.