CMake Overview
This article is part of a series.
Historically I’ve built C++ as tiny specific programs for tiny specific chips. This means not even using regular gcc, but avr-gcc or arm-gcc. Honestly I haven’t even used clang a ton until starting with Swift. I’ve written Makefiles, and run the occasional provided CMake file. Understanding CMake has hardly been necessary since CMake shines for large software projects that need to be built on several platforms and that isn’t what I’ve generally done.
Now however, OpenUSD has a pretty impressive build script and resulting CMake files that I’d like to understand better if I may ultimately have to do a specialized build of it. Also, if I’m going to mix C++ and Swift, understanding the build systems better will only help.
Simplest CMake
The simplest main CMake file (always called CMakeLists.txt) has 3 parts
#PART 1: Minimum Version of CMake file is valid for
cmake_minimum_required(VERSION 3.24)
#PART 2: Project Name (technically optional)
project(Hello)
#PART 3: Executable Target name and files list
add_executable(ClickHello HelloWorld.cpp)
Imagine a project with this layout:
└── Project
└── source
│ └── CMakeLists.txt
| └── HelloWorld.cpp
└── build
└── nothing yet...
To build the source files into the build folder there are a couple of ways to go about it:
# The classic Style
cd $BUILD_DIR
cmake $SOURCE_DIR # the location of the CMakeLists.txt
make # assumes CMake made Makefiles
./$EXECUTABLE_NAME #run the code
## Modern Style
cd $SOURCE_DIR
cmake -S . -B ../build
cmake --build ../build #can be run no matter the build system
../build/$EXECUTABLE_NAME
The makefile generated by this simplest of processes already has huge amounts more thrown in than the ones I’ve written in the past.
Example CMake for Multi-File Project
To test CMake for a multi-file project, I made a simple prime number supplier project with the following file layout:
└── Project
└── source
│ └── CMakeLists.txt
| └── resources
| | └── prime_numbers.txt
| └── Prime.cpp
| └── PrimeLib.cpp
| └── PrimeLib.hpp
└── build
└── PrimeEmitter
└── resources
└── prime_numbers.txt
Note that the text file with the primes is copied into the build folder (at compile time), NOT mashed into the binary. That’s accomplished by the last part of the CMakeLists.txt:
# Set the minimum required version
cmake_minimum_required(VERSION 3.24)
# Project Information
project(Primes
VERSION 0.0.1
DESCRIPTION "Complete Miscellany"
LANGUAGES CXX)
#Name the library and the files it uses
add_library(PrimeNumberStore PrimeLib.cpp PrimeLib.hpp)
# Name the Executable Target and say which files it uses
add_executable(PrimeEmitter Prime.cpp)
target_compile_features(PrimeEmitter PRIVATE cxx_std_20)
# Link the library
target_link_libraries(PrimeEmitter PRIVATE PrimeNumberStore)
# Example of add_custom_command(), Move a Resources Folder
# Uses cleaner $<TARGET_FILE_DIR:PrimeEmitter> instead of ${CMAKE_CURRENT_BINARY_DIR}
add_custom_command(
TARGET PrimeEmitter POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/resources $<TARGET_FILE_DIR:PrimeEmitter>/resources
)
- Scroll down after resources to see files inline or on github
It all worked great using:
cd $PROJECT_FOLDER/source
cmake -S . -B ../build # must do prep
cmake --build ../build # before can build
../build/PrimeEmitter # gives random prime
../build/PrimeEmitter 3 998 9999 # gives 3rd, 998th and 9999th prime
References
- The book everyone recommends: https://crascit.com/professional-cmake/
- Best overview video by “C++ Weekly With Jason Turner” and some updates
- episode 78 Overview of what CMake does. Simple. Fast. This is the winner.
- episode 208, Catalog of C++ CMake options. Dense. A reference not a how to.
- episode 376 2023 Update, also dense. Again, a reference for those already knowledgeable.
- Older Repo mentioned in 78 (GUI Starter)
- 2023 Repo from 376(CMake Template)
- Nice CMake syntax overview: https://llvm.org/docs/CMakePrimer.html
- I got those from this excellent list of tips
- Which I got from this reference: https://cliutils.gitlab.io/modern-cmake/
- https://kubasejdak.com/19-reasons-why-cmake-is-actually-awesome
- CMake Series by Riley Entertainment Games Gets to the point. Vaguely follows along with official tutorial.
- Extensive video intro series. Windows & Linux focused. Can really skip to 14:23 in the to second video unless need help setting up from 0. Very very detailed. Great for people starting utterly from scratch.
- Official Tutorial
- An interesting “Best Practices” video to watch AFTER some familiarity with CMake files. CppCon 2017: Mathieu Ropert “Using Modern CMake Patterns to Enforce a Good Modular Design”
- Also not an introduction, ~2hrs Daniel Pfeifer - “Effective CMake”
File Contents for Multi-File project
Prime.cpp
#include "PrimeLib.hpp"
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
if (argc == 1) {
cout << random_prime() << " ";
} else {
for (int i = 1; i <= argc - 1; i++) {
//in a real program would want to handle error
const long num = std::stoi(argv[i]);
cout << prime_at(num) << " ";
}
}
cout << endl;
return 0;
}
PrimeLib.hpp
#ifndef PRIME_INCLUDED
#define PRIME_INCLUDED
//prime in first 10000 primes at index
int prime_at(int index);
//random prime within first 10000 primes
int random_prime();
#endif
PrimeLib.cpp
#include "PrimeLib.hpp"
#include <fstream>
#include <random>
#include <string>
// Max line is 12 characters long
const int max_line_length = 16;
// 10,000th prime is 104729
const int max_prime_length = 6;
int readNthLine(std::ifstream &in, int index) {
for (int i = 0; i < index - 1; ++i) {
// if don't know max line length
// in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
in.ignore(max_line_length, '\n');
}
// What about a char *p instead of string?
// Why/Why not do that in "Modern C++"
std::string s;
s.reserve(max_prime_length);
std::getline(in.ignore(max_line_length, ' '), s);
return std::stoi(s);
};
// just because.
// https://stackoverflow.com/questions/5008804/generating-a-random-integer-from-a-range
template <typename T> T fancy_random(const T &min, const T &max) {
std::random_device seed_maker;
std::mt19937 generator(
seed_maker()); // Random-number engine to use (Mersenne-Twister)
std::uniform_int_distribution<T> distribution(min,
max); // Guaranteed unbiased
return distribution(generator);
}
// random prime within first 10000 primes
int random_prime() { return prime_at(fancy_random(0, 10000)); }
// prime in first 10000 primes at index
int prime_at(int index) {
std::ifstream primesFile("resources/b000040.txt");
if (primesFile.is_open()) {
return readNthLine(primesFile, index);
} else {
// handle error
return -1;
}
}
Prime Numbers text
The supplied prime numbers file has the prime’s index number followed by the prime itself. From the The On-Line Encyclopedia of Integer Sequences
249 1579
250 1583
251 1597
252 1601
253 1607
254 1609
255 1613
256 1619