In this post we will lay the foundation for a basic CMake project that uses the Pitchfork Layout (PFL).
Organizing Files and Folders
On the internet you find many topics on how to organize your files and folders.
After several years of trying different folder structures, I now stick with the Pitchfork Layout (PFL).
When I first came across it, I was a bit overwhelmed to be honest, as I first thought it was over-engineered.
Digging deeper however you find that most of the folders aren’t required (by design) and often not needed in every project. Especially the build-in capability of submodules makes PFL very adaptive to project sizes.
The author of PFL provides an example on github.
CMakeLists.txt – Entry Point
Independent of how you organize your files, when you try to run CMake it will ask for a path to your source files.
CMake will search for a CMakeLists.txt which makes the entry point to our project.
We use this to do some project-wide configuration, including:
- Define CMake minimum version (cmake_minimum_required)
- Give a project name
- Specify a (required) C++ standard that the system has to support
- Optional: Find some dependencies
This usually boils down to something like this:
cmake_minimum_required(VERSION 3.11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
project(MyProject VERSION 1.0.0)
find_package(Threads REQUIRED)
add_subdirectory(src)
When CMake names something “required” and a condition cannot be fulfilled usually an error is raised and the project cannot be generated. It is therefore very straight forward to see what your project needs:
- CMake Version 3.11 or later
- C++ 17 capable compiler
- CMake must be able to find the package Threads
After that we continue to the src/ folder.
The Source Folder
Inside this folder we can create CMakeLists.txt file to define a new CMake target:
# CMakeLists.txt in src/
add_executable(MyExecutable main.cpp)
target_link_libraries(MyExecutable PRIVATE Threads::Threads)
The target called MyExecutable is built with main.cpp. You can add more files to the list of source files or use target_sources.
We also tell CMake that our executable needs the Threads package. I will omit the .cpp content here, imagine it some basic Hello World code.
Current files and folders of our project:
CMakeLists.txt
src/
– CMakeLists.txt
– main.cpp
Configure and generate
cmake .
Build
cmake --build .
Adding more targets
So far this is a pretty basic setup. We now extend that with an examples folder and make a more use of CMake.
We create the following project structure:
CMakeLists.txt
examples/
– CMakeLists.txt
– MyExample/
– CMakeLists.txt
– example.cpp
src/
– CMakeLists.txt
– main.cpp
Again we need a CMakeLists.txt to define a target inside examples/MyExample/ similar to our main target.
# CMakeLists.txt in examples/MyExample/
add_executable(MyExample example.cpp)
target_link_libraries(MyExample PRIVATE Threads::Threads)
You could go ahead and call add_subdirectory from the root CMakeLists with add_subdirectory(examples/MyExample).
The drawback of this approach: every time you add (or remove) an example you will have to modify your root CMakeLists and sooner or later it is cluttered with calls to add_subdirectory.
A better approach
Create one CMakeLists.txt inside the examples/ folder with the only purpose to add sub directories.
# CMakeLists in examples/
add_subdirectory(MyExample)
# Add more if needed later
Extend the root CMakeLists.txt:
cmake_minimum_required(VERSION 3.11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
project(MyProject VERSION 1.0.0)
find_package(Threads REQUIRED)
add_subdirectory(src)
add_subdirectory(examples) # <------
Configure, Generate and build:
cmake . && cmake --build .
Expand your project
You can apply this pattern to other directories and parts of your project. A good candidate are unit-tests.
Use a configurable flag with CMake’s – if for the user to set. This way the user can include / exclude sub directories during the configure step.
Another plus: Readers can easily follow the chain of add_subdirectory to understand what is going on inside the project, which is especially helpful when the project grows larger.