If you have ever tried to compile a multi-file C++ server project by hand with g++, you know how quickly the command line turns into a nightmare: dozens of .cpp files, include paths, external libraries, and a project that recompiles from scratch on every change. This is exactly where the question what is CMake comes in. CMake is a cross-platform build system generator that lets you describe how your project is built in a single recipe file. In this article we will set up CMake from scratch using a game/chat server as our example.
What is CMake and why do you need it?
CMake is not a compiler itself. It reads the CMakeLists.txt file you write and generates the actual build files suited to your system: usually Makefiles on Linux, Visual Studio projects on Windows, or Ninja files if you prefer. In other words, CMake is a "meta build system": you write one recipe, and it works on every platform.
- Portability: The same
CMakeLists.txtruns on your Linux VPS and on the Windows machine you develop on. - Dependency management: CMake tracks which file depends on which, and recompiles only the files that changed.
- Library linking: You pull in libraries like pthread, OpenSSL or Boost with a single line.
Server projects have exactly these three needs: many files, external libraries, and the ability to build in different environments. That is why CMake is practically the standard for any serious C++ server.
Example project layout
Say we are writing a simple TCP server. Let us split the files into sensible folders; this matters both for readability and for the CMake configuration.
server/
├── CMakeLists.txt
├── include/
│ └── server/
│ ├── Server.hpp
│ └── Connection.hpp
├── src/
│ ├── main.cpp
│ ├── Server.cpp
│ └── Connection.cpp
└── build/ (build output goes here)
Keeping headers under include/ and source files under src/ is a common layout. The build/ folder is for the temporary files CMake generates and is usually added to .gitignore.
Your first CMakeLists.txt
Now let us write a CMakeLists.txt in the root directory. I will go through each line and what it does.
cmake_minimum_required(VERSION 3.16)
project(GameServer VERSION 1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(server
src/main.cpp
src/Server.cpp
src/Connection.cpp
)
target_include_directories(server PRIVATE include)
Line by line:
cmake_minimum_required: Declares the lowest CMake version you want to support. 3.16 is a reasonable baseline for modern features.project(...): Defines the project name, version and language (CXX= C++).set(CMAKE_CXX_STANDARD 17): Uses the C++17 standard. WithSTANDARD_REQUIRED ON, the build fails if that standard is unavailable instead of silently falling back.add_executable: Creates an executable target namedserverand tells CMake which sources to build it from.target_include_directories: Tells the compiler to look for headers underinclude/.PRIVATEmeans this setting belongs only to this target.
Building the project: out-of-source builds
The recommended way to build with CMake is to keep build files separate from the source tree. So we work inside a dedicated build/ folder:
mkdir build
cd build
cmake ..
cmake --build .
The cmake .. command reads the CMakeLists.txt in the parent folder and generates the build system (this step is called configure). Then cmake --build . runs the generated Makefile or Ninja file to do the actual compilation. This command is platform-independent and more portable than calling make directly. The result is an executable named server inside build/.
Linking libraries and splitting into modules
Servers are rarely standalone. They usually need threads (pthread), encryption (OpenSSL) or other libraries. CMake solves this with find_package and target_link_libraries:
find_package(Threads REQUIRED)
target_link_libraries(server PRIVATE Threads::Threads)
find_package(Threads REQUIRED) locates the system's threading library; thanks to REQUIRED, the configure step stops if it cannot be found. We then link the Threads::Threads target into server. This is the "modern CMake" approach: you link libraries as targets rather than bare names, so include paths and compiler flags come along automatically.
As your project grows, it makes sense to pull part of the code into a separate library. For example, you can turn the network layer into a reusable library:
add_library(netcore
src/Server.cpp
src/Connection.cpp
)
target_include_directories(netcore PUBLIC include)
add_executable(server src/main.cpp)
target_link_libraries(server PRIVATE netcore Threads::Threads)
Here netcore is a library target, and thanks to PUBLIC include, anything that links against it automatically inherits the include/ path. This is a powerful pattern that avoids repetition in large projects.
Debug, Release and practical tips
While developing the server you want debug information; when shipping you want optimization. CMake manages this through build types:
cmake -DCMAKE_BUILD_TYPE=Release ..
Debug adds symbols and disables optimization; Release turns on optimizations like -O2. A few practical suggestions:
- Turn on warnings:
target_compile_options(server PRIVATE -Wall -Wextra)catches hidden bugs early. - Always add the
build/folder to.gitignore; never commit generated files to version control. - For faster builds, use Ninja:
cmake -G Ninja ..is a one-line change but much faster at parallel compilation.
Frequently Asked Questions
What is the difference between CMake and Make?
Make is a direct build tool that runs Makefiles. CMake generates those Makefiles (or other formats like Ninja and Visual Studio projects) for you. So CMake works at a higher, platform-independent level, while Make processes the output it produces.
Should I list every file in add_executable individually?
Yes, that is the most reliable way. While you can auto-collect files with file(GLOB ...), CMake may not notice when you add a new file. Listing sources explicitly is the recommended approach.
Should I commit the build folder to version control?
No. build/ contains entirely generated, machine-specific files. Add it to .gitignore; anyone can regenerate it on their own machine with cmake ...
Need a solid build setup for your server project? Whether it is a CMake configuration from scratch or getting an existing C++ project to compile, we can sort it out together. Get in touch with me.