CMake by Example
CMake is an open source cross-platform meta build system that works on Windows, macOS, Linux, and more. It is designed to bridge the gap between the platform-specific build tools such as Ninja, GNU Make, BSD Make, Visual Studio, and Xcode. You can read more about CMake on the official CMake.org website. CMake is best known for its unmatched popularity as the primary C/C++ build tool.
CMake by Example is a hands-on introduction to how to use CMake to build C/C++ projects using annotated example projects. Make sure you have installed the latest version of CMake on your system. Check out the first Executable example to get started.
- Executable
- Library
- Variables
- Control flow
- Set C/C++ standard
- Platform detection
- Compiler detection
- Feature detection
- Compiler flags
- Toolchain file
- Clang
- GCC
- MSVC
- zig cc
- cosmocc
- WASI SDK
- Emscripten
- Testing
- Catch2
- Doxygen
- clang-format
- clang-tidy
- cppcheck
- cmake-format
- FindPackage
- FetchContent
- Conan 2
- vcpkg
Executable
Filename: CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(executable LANGUAGES C CXX)
add_executable(hello-world ./src/bin/hello-world.c)
add_executable(goodbye-world ./src/bin/goodbye-world.cpp)
Filename: src/bin/hello-world.c
#include <stdio.h>
int main() {
printf("Hello, %s!\n", "World");
return 0;
}
Filename: src/bin/goodbye-world.cpp
#include <iostream>
int main() {
std::cout << "Goodbye, " << "World" << "!\n";
return 0;
}
$ cmake -B ./build/
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (5.6s)
-- Generating done (0.0s)
-- Build files have been written to: /workspaces/cmakebyexample.jcbhmr.com/listings/02-executable/build
$ cmake --build ./build/
[ 25%] Building C object CMakeFiles/hello-world.dir/src/bin/hello-world.c.o
[ 50%] Linking C executable hello-world
[ 50%] Built target hello-world
[ 75%] Building CXX object CMakeFiles/goodbye-world.dir/src/bin/goodbye-world.cpp.o
[100%] Linking CXX executable goodbye-world
[100%] Built target goodbye-world
$ ./build/hello-world
Hello, World!
$ ./build/goodbye-world
Goodbye, World!
Library
Filename: CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(library LANGUAGES C)
add_library(greeter ./src/lib.c)
target_include_directories(greeter PUBLIC ./include)
Filename: src/lib.c
#include <greeter.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *greeting_for(char const *name) {
char *greeting = malloc(strlen(name) + 8);
sprintf(greeting, "Hello, %s!", name);
return greeting;
}
Filename: include/greeter.h
#pragma once
char *greeting_for(char const *name);
$ cmake -B ./build/
-- The C compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /workspaces/cmakebyexample.jcbhmr.com/listings/03-library/build
$ cmake --build ./build/
[ 50%] Building C object CMakeFiles/greeter.dir/src/lib.c.o
[100%] Linking C static library libgreeter.a
[100%] Built target greeter
$ nm ./build/libgreeter.a
lib.c.o:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T greeting_for
U malloc
U sprintf
U strlen
Variables
Filename: CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(variables LANGUAGES C)
message("CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}") # "/workspace" or similar
message("CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}") # "/workspace/build" or similar
message("CMAKE_C_COMPILER=${CMAKE_C_COMPILER}") # "/usr/bin/cc" or similar
message("CMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}") # "/usr/bin/c++" or similar
message("CMAKE_AR=${CMAKE_AR}") # "/usr/bin/ar" or similar
message("CMAKE_RANLIB=${CMAKE_RANLIB}") # "/usr/bin/ranlib" or similar
message("PROJECT_NAME=${PROJECT_NAME}") # "variables"
message("CMAKE_HOST_UNIX=${CMAKE_HOST_UNIX}") # TRUE if the host system is UNIX-like
message("CMAKE_WIN32=${CMAKE_WIN32}") # TRUE if the target system is Windows
set(MY_BOOL ON) # BOOL type
set(MY_EXE_NAME "variables") # STRING type
set(MY_SOURCE_FILES "./src/main.c" "./src/stats.c") # list type
add_executable("${MY_EXE_NAME}" ${MY_SOURCE_FILES})
Filename: src/main.c
#include <stdio.h>
#include "./stats.h"
int main() {
printf("Hello, %s!\n", "World");
printf("The average of 1, 2, and 3 is %f.\n", average_of_three(1, 2, 3));
return 0;
}
Filename: src/stats.c
#include "./stats.h"
double average_of_three(double a, double b, double c) {
return (a + b + c) / 3;
}
Filename: src/stats.h
#pragma once
double average_of_three(double a, double b, double c);
$ cmake -B ./build/
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMAKE_SOURCE_DIR=/workspaces/cmakebyexample.jcbhmr.com/listings/03-variables
CMAKE_BINARY_DIR=/workspaces/cmakebyexample.jcbhmr.com/listings/03-variables/build
CMAKE_C_COMPILER=/usr/bin/cc
CMAKE_CXX_COMPILER=/usr/bin/c++
CMAKE_AR=/usr/bin/ar
CMAKE_RANLIB=/usr/bin/ranlib
PROJECT_NAME=variables
CMAKE_HOST_UNIX=1
CMAKE_WIN32=
-- Configuring done (0.4s)
-- Generating done (0.0s)
-- Build files have been written to: /workspaces/cmakebyexample.jcbhmr.com/listings/03-variables/build
$ cmake --build ./build/
[ 33%] Building C object CMakeFiles/variables.dir/src/main.c.o
[ 66%] Building C object CMakeFiles/variables.dir/src/stats.c.o
[100%] Linking C executable variables
[100%] Built target variables
$ ./build/variables
Hello, World!
The average of 1, 2, and 3 is 2.000000.
Control flow
Filename: CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(control-flow LANGUAGES C)
foreach(varname IN ITEMS WIN32 UNIX APPLE)
if("${${varname}}") # Double expansion. "${WIN32}" or similar.
add_executable("${varname}-only" "./src/bin/${varname}-only.c")
endif()
endforeach()
Filename: src/bin/UNIX-only.c
#include <stdio.h>
int main() {
printf("Hello, %s!\n", "UNIX");
return 0;
}
Filename: src/bin/APPLE-only.c
#include <stdio.h>
int main() {
printf("Hello, %s!\n", "APPLE");
return 0;
}
Filename: src/bin/WIN32-only.c
#include <stdio.h>
int main() {
printf("Hello, %s!\n", "WIN32");
return 0;
}
$ cmake -B ./build/
-- The C compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done (0.3s)
-- Generating done (0.0s)
-- Build files have been written to: /workspaces/cmakebyexample.jcbhmr.com/listings/04-control-flow/build
$ cmake --build ./build/
[ 50%] Building C object CMakeFiles/UNIX-only.dir/src/bin/UNIX-only.c.o
[100%] Linking C executable UNIX-only
[100%] Built target UNIX-only
$ ./build/UNIX-only
Hello, UNIX!
Set C/C++ standard
Filename: CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(set-c-cxx-standard LANGUAGES C CXX)
add_executable(hello-c23 ./src/bin/hello-c23.c)
target_compile_features(hello-c23 PRIVATE c_std_23)
add_executable(hello-cxx23 ./src/bin/hello-cxx23.cpp)
target_compile_features(hello-cxx23 PRIVATE cxx_std_23)
Filename: src/bin/hello-c23.c
#include <stdio.h>
int main() {
printf("__STDC_VERSION__=%ld\n", __STDC_VERSION__);
return 0;
}
Filename: src/bin/hello-cxx23.cpp
#include <iostream>
int main() {
std::cout << "__cplusplus=" << __cplusplus << "\n";
return 0;
}
$ CC=clang-18 CXX=clang++-18 cmake -B ./build/
-- The C compiler identification is Clang 18.1.8
-- The CXX compiler identification is Clang 18.1.8
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/clang-18 - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/clang++-18 - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.8s)
-- Generating done (0.0s)
-- Build files have been written to: /workspaces/cmakebyexample.jcbhmr.com/listings/05-set-c-cxx-standard/build
$ cmake --build ./build/
[ 25%] Building C object CMakeFiles/hello-c23.dir/src/bin/hello-c23.c.o
[ 50%] Linking C executable hello-c23
[ 50%] Built target hello-c23
[ 75%] Building CXX object CMakeFiles/hello-cxx23.dir/src/bin/hello-cxx23.cpp.o
[100%] Linking CXX executable hello-cxx23
[100%] Built target hello-cxx23
$ ./build/hello-c23
__STDC_VERSION__=202311
$ ./build/hello-cxx23
__cplusplus=202302
When you run this example yourself make sure you have a compiler that supports C23 and C++23. You can set the C and C++ compiler that CMake will use by setting the CC
and CXX
environment variables.