CMake basics, how does one write a good CMake project? (2024)

03/16/2020

cmake,tutorial,C++,buildsystem

Why CMake?

At siliceum, we decided to use CMake for our C++ based projects. There are many (relatively) new (meta) buildsystems around lately such as build2 or meson.

We based this choice on the fact that most of the ecosystem is using CMake, so IDEs now have decent support for it, so do most libraries and major package managers.It also requires no additional dependency such as Python which usually makes it a tiny bit easier to install for Continuous Integration on platforms such as Windows.So while CMake is far from perfect, for now, it does the job.

But how does one write a good CMake project?We hope to answer this question so that the C++ community can thrive, and make it easier for everyone to integrate any library in a project.

This is the first article of a mini series related to build systems and continuous integration.

The CMakeLists.txt file

All build systems require some kind of entrypoint for the definition of the project. CMake uses a file named CMakeLists.txt, and it is written in its own scripting language.

CMake version specification

The first thing you will need in it, is to specify the minimum version of CMake you will be using. This is important because CMake can have different behaviours based on its versions, which are named policies.

Depending on the features you will be using, you might need to ask for more recent versions of CMake. In our case we choose CMake 3.14 as it is not too old and supports most recent features.

Please do not use something older than 3.1, which dates back to 2014!

cmake_minimum_required(VERSION 3.14)

Project description

Then, you'll want to specify the name, languages and version (needed for packaging purposes).This can be done easily with the project command:

project(YOUR_PROJECT_NAME VERSION 0.1.0 LANGUAGES C CXX)

Comments

Comments start with the character # and can be multiline (bracket comments) if following the bracket_open syntax.

# This is a single line comment

Targets

The different types of targets

A target usually is an executable or a library, but can also be a custom target if your project needs to run some custom tools.
Those can be created respectively using the commands add_executable, add_library and add_custom_target.

Dependencies between targets can then be defined to determine the build order and link commands.

A sample project

Let's say we want to create a project with a library, and a commandline interface application using it.You can start by telling cmake what files are used to build the library. Note that we will be using the pitchfork convention for file layout.

add_library(myawesomelib source/myawesomelib.cpp source/implementation-details.h include/myawesomelib.h)

Since we put our public headers in a different directory than the other source files, we'll need to tell the compiler where to find those. This is done with target_include_directories.

target_include_directories(myawesomelib PUBLIC include)

Target properties and transitive usage requirements

You probably wonder what the PUBLIC parameter means.In CMake, targets have a list of properties that are used when building, and you can populate those using various commands. Some of those properties are called transitive usage requirements and can be propagated from one target to another when a dependency is declared.

There are 3 keywords controlling the propagation:

  • PRIVATE means there is no propagation and the property is only applied to the target.
  • INTERFACE means the property will be propagated but not applied to the target, it is only used as part of the interface.
  • PUBLIC means it is both used by the target and propagated to its dependents.

This means that when linking our myawesomelib, we will be able to include myawesomelib.h, and we will also be able to include it from myawesomelib.cpp.

In the case of a header only library, you need to tell CMake that no compilation is required by creating an interface library. It will use only INTERFACE properties.This is done by calling add_library without sources and the INTERFACE keyword, for example add_library(myheaderonlylib INTERFACE).

Other types of libraries exist but won't be covered in this article, refer to the documentation for an exhaustive list.

Same thing for more information about targets, transitive usage requirements and the buildsystem in general, you can take a look at the documentation here.

Linking libraries

Now that we have a library, we want to use it in our commandline tool.We will use the add_executable command to create the executable target, and target_link_libraries to indicate what libraries we are using:

add_executable(myawesomecli main.cpp)target_link_libraries(myawesomecli PRIVATE myawesomelib)

And that's it!

Building with cmake

It is highly recommended to create a build directory before building a CMake project so that it doesn't pollute the rest of your project directory. That way you can also have one build directory per compiler or platform for example.

Then simply place yourself in this directory (usually called build) and start CMake from it.You can use CMake-GUI (which is very helpful for configuration) or use the cmake command line directly.

CMake is meta-buildsystem, this means that it does not compile your code directly but will generate projects for other buildsystems. The main advantage of this technique is that it can generate files that are used by default by your platform and/or IDE. This is handled by chosing a generator (or using the default one).

Viewed from the commandline perspective, you basicly need to do the following:

mkdir buildcd buildcmake -G "Your generator" ..

And you have generated the files needed to build your project. You can omit the -G "Your generator" and cmake will chose a generator by default. It can be a Visual Studio project on Windows or a Makefile on Linux.

In both cases, you can build your project directly using the cmake commandline instead of using the underlying buildsystem, effectively working as an abstraction:

cmake --build . [--target yourtarget]

Note that you might prefer to work from the project root directory and not change your working directory to the build one. This can be done with the following command lines:

cmake -B build -G "Your generator" -S .cmake --build build

More control

Keep your dependencies under control

As you might have guessed, transitive properties are really practical as they can propagate across multiple levels of indirection when linking targets.
That's also why it is recommended to always specify the propagation type. If one of your library needs to link against another but does not want to expose it, for example if you are wrapping different libraries, link against those with PRIVATE.

Of course you can also mix PRIVATE, INTERFACE and PUBLIC libraries for a single target

add_library(mywrapper [...])target_link_libraries(mywrapper PRIVATE otherLib1 otherLib2 PUBLIC libNeededByConsumers)

Note that if a static library A links against library B privately, a binary using it will link against both as it is necessary, but only the properties of A will propagate to it.

Compile definitions and features

Often you will need to pass macro definitions to the compiler, this is done with target_compile_definitions.

For example:

target_compile_definitions(myawesomelib PRIVATE USE_SIMD=1 INTERNAL_MACRO INTERFACE ONLY_CONSUMERS_CAN_SEE_THIS_DEFINE=42)

If you are using recent features of C++, you will want to specify which ones you are using.
CMake will automatically detect what flags need to be added to the compiler command and if it is not supported by your compiler, will show you an error.For exemple if you want to require the usage of the c++14 standard:

target_compile_features(myawesomelib PRIVATE cxx_std_14)

While CMake used to provide more fine grained control, it is now mostly exposing only the standard versions.
The list of compile features for C++ are available here.

You can also use target_compile_options to pass flags directly to the compiler.
However if you decide to do so, it is best to make it optional if the flag is non-mondatory. More details about this in the next article!

Splitting the CMakeLists.txt

As projects grow, you quickly need to organize your files and splitting the CMakeLists.txt file becomes necessary.

Example structure

Let's assume the following filesystem tree, with 3 subprojects libA, libB and programA.We'll have libB depend on libA and programA depend on libB, but libA should not leak properties into programA.

myawesomeproject└── libs ├── libA │ └── include │ └── libA.h ├── libB │ ├── include │ │ └── libB.h │ └── src │ └── libB.cpp └── programA └── src └── main.cpp

The suggested approach is to have CMakeLists.txt files placed the following way:

myawesomeproject├── CMakeLists.txt└── libs ├── CMakeLists.txt ├── libA │ ├── CMakeLists.txt │ └── include │ └── libA.h ├── libB │ ├── CMakeLists.txt │ ├── include │ │ └── libB.h │ └── src │ └── libB.cpp └── programA ├── CMakeLists.txt └── src └── main.cpp

Content of the CMakeLists.txt files

Now that we have our directory structure, what do we put in our files?

Subfolders

CMake has two main ways of handling multi-directories projects, the add_subdirectory and include commands.

If you use add_subdirectory, you will be creating a new scope for variables, while with include, variables will be declared in the current scope. Both have their use case.
We advise to use add_subdirectory by default. Note that include can also be used in the context of CMake Modules which we will discuss in a later post.

When calling add_subdirectory(dir), CMake will add dir to the build by opening dir/CMakeLists.txt.

In some cases you might want to add directories but not have them be built by default unless required, either by dependency or by the user.
In that case you can add the EXCLUDE_FROM_ALL parameter which will effectively tell CMake not to add the targets from the subdirectory to the dependencies of the all target nor in the IDEs (think make all). eg add_subdirectory( optionalTargetsSubDir EXCLUDE_FROM_ALL ).

You will also need to call add_subdirectory in the correct order if you want to have dependencies across the different subdirectories.

A view of a minimalistic content of the files:

./CMakeLists.txt

cmake_minimum_required(VERSION 3.14)project(myawesomeproject VERSION 0.10 LANGUAGES C CXX)add_subdirectory(libs)

libs/CMakeLists.txt

add_subdirectory(libA)add_subdirectory(libB) # After libA so that we can link itadd_subdirectory(programA) # After libB so that we can link it

libs/libA/CMakeLists.txt

add_library(libA INTERFACE)target_include_directories(libA INTERFACE include/)

libs/libB/CMakeLists.txt

add_library(libB src/libB.cpp include/libB.h )target_include_directories(libB PUBLIC include/)# PRIVATE so that libA doesn't leak into programAtarget_link_libraries(libB PRIVATE libA)

libs/programA/CMakeLists.txt

add_executable(programA src/main.cpp)target_link_libraries(programA PRIVATE libB)

What's next?

This article covered the basics of modern CMake, and should be enough to give you a headstart.

Some topics were deliberatly not addressed such as variables, build configurations, toolchain files, modules and packages as those can be less simple than they seem.

In the next article we will dive a bit deeper and take a look at variables, configurations and generator expressions.

For those in a quest for knowledge and can't wait for the next article, we recommend having a look at this C++ boilerplate. It is a concrete example of a C++ project using modern CMake, from which you can start and build on.

And of course, do not forget the official CMake documentation.

You can discuss this article on reddit r/cpp.

Clément Grégoire
CMake basics, how does one write a good CMake project? (2024)

FAQs

How do I create a simple CMake project? ›

CMake Tutorial
  1. A Basic Starting Point (Step 1) ...
  2. Adding a Library (Step 2)
  3. Adding Usage Requirements for Library (Step 3)
  4. Installing and Testing (Step 4) ...
  5. Adding System Introspection (Step 5) ...
  6. Adding a Custom Command and Generated File (Step 6)
  7. Building an Installer (Step 7)
  8. Adding Support for a Dashboard (Step 8)

What is the difference between CMake project and makefile project? ›

CMake and Make work on different principles. With CMake you specify build targets, and assign source files and various parameters. With Make, you create a set of build rules. Each rule has a target file, a set of dependencies (to the right of the colon), and the commands needed to perform the rule's build task.

How does CMake work? ›

CMake is a meta build system that uses scripts called CMakeLists to generate build files for a specific environment (for example, makefiles on Unix machines). When you create a new CMake project in CLion, a CMakeLists. txt file is generated automatically under the project root.

Does CMake generate makefiles? ›

CMake may support multiple native build systems on certain platforms. A makefile generator is responsible for generating a particular build system. Possible generator names are specified in the Generators section. -Wno-dev : Suppress developer warnings.

What is the CMake language? ›

CMake has a relatively simple interpreted, imperative scripting language. It supports variables, string manipulation methods, arrays, function/macro declarations, and module inclusion (importing). CMake Language commands (or directives) are read by cmake from a file named CMakeLists. txt .

Why is CMake popular? ›

It allows a project to use its most important resource, the developer, to their full potential. As CMake supports many popular IDE systems for C++ as well as command-line build tools, developers are able to choose the build tool that they are most productive with.

What is ninja in CMake? ›

Ninja is a small build system with a focus on speed. It differs from other build systems in two major respects: it is designed to have its input files generated by a higher-level build system, and it is designed to run builds as fast as possible.

How to build a CMake project in Linux? ›

Build and Run

First, run the cmake executable or the cmake-gui to configure the project and then build it with your chosen build tool. Then call that build system to actually compile/link the project: cmake --build . Note: Depending on the shell, the correct syntax may be Tutorial , ./Tutorial or .\Tutorial .

Why use bazel over CMake? ›

The advantages of Bazel include native support for remote execution and distributed builds, built-in caching mechanism for faster builds, support for multiple programming languages and platforms, declarative build language for ease of use, and advanced dependency management system for reproducibility and scalability.

Should I use CMake or Visual Studio? ›

CMake is a better build system than Visual Studio projects and solutions. It is compact and much more easier to maintain even for Windows only projects. See CMake for Visual Studio Developers for some reasons.

Why is Ninja better than make? ›

In essence, Ninja is meant to replace Make, which is slow when performing incremental (or no-op) builds. This can considerably slow down developers working on large projects, such as Google Chrome which compiles 40,000 input files into a single executable.

What problems does CMake solve? ›

CMake handles the difficult aspects of building software such as cross-platform builds, system introspection, and user customized builds, in a simple manner that allows users to easily tailor builds for complex hardware and software systems.

Does CMake need a compiler? ›

If you do not find precompiled binaries for your system, then you can build CMake from source. To build CMake, you will need a modern C++ compiler and the source distribution from the CMake Download page or Kitware's GitLab instance. To build CMake, follow the instructions in README. rst at the top of the source tree.

What are the principles of CMake? ›

Two principles of CMake you have to keep in mind:
  • CMake is a script language and arguments are evaluated after the variables are expanded.
  • CMake differentiates between normal strings and list variables (strings with semicolon delimiters)
Mar 7, 2016

How do I create a simple .NET project? ›

To get started, go to the Explorer view and select Create .NET Project. Alternatively, you can bring up the Command Palette using Ctrl+Shift+P and then type ".NET" and find and select the .NET: New Project command. After selecting the command, you'll need to choose the project template.

How do I create a mini project file? ›

Follow these steps to make mini project in college:
  1. Find a simple topic that interests you.
  2. Draft a clear-cut plan before you start working on your project.
  3. List 3 potential topics and choose the one that fits your future career goals.
  4. Always have a backup project idea & plan ready.

How do I create a simple node project? ›

Setting Up a Node. js Project
  1. nvm install node.
  2. npm init.
  3. npm install express.
  4. const express = require('express');
  5. const app = express();
  6. const port = 3000; app. listen(port, () => { console. log(`Server listening on port ${port}`); });
Mar 5, 2024

How do I create a Makefile project? ›

Here's a step-by-step guide to creating a simple Makefile:
  1. Choose a Text Editor: Use a text editor of your choice to create and edit your Makefile. ...
  2. Create a File: ...
  3. Define Targets and Rules: ...
  4. Write Rules: ...
  5. Adding More Rules: ...
  6. Indentation: ...
  7. Save the Makefile: ...
  8. Run Make Commands:
Aug 24, 2023

References

Top Articles
CSR 2 Realistic Drag Racing – Best Cars to Use in Each Tier | BlueStacks
???? CSR Best Cars To Buy - Car Buying Guide + Best Tuning
2016 Hyundai Sonata Refrigerant Capacity
Pwc Transparency Report
Episode 163 – Succession and Legacy • History of the Germans Podcast
799: The Lives of Others - This American Life
Fbsm Berkeley
Academic Calendar Biola
Mashle: Magic And Muscles Gogoanime
Food And Grocery Walmart Job
Craigslist Pinellas County Rentals
Estate Sales Net Grand Rapids
Black Adam Showtimes Near Kerasotes Showplace 14
Jinx Manga Vyvy
Naughty Neighbor Tumblr
Armslist Dayton
1800Comcast
Pritzker Sdn 2023
라이키 유출
Offsale Roblox Items are Going Limited… What’s Next? | Rolimon's
R/Chinatime
Alishbasof
Nu Do Society Menu
Wwba Baseball
Tyrone's Unblocked Games Basketball
Anvil In Shattrath
Los Garroberros Menu
Tri-State Dog Racing Results
Used Fuel Tanks For Sale Craigslist
Killing Self Gif
Wym Urban Dictionary
A Closer Look at Ot Megan Age: From TikTok Star to Media Sensation
Forums Social Media Girls Women Of Barstool
New R-Link system and now issues creating R-Link store account.
Planet Zoo Obstructed
Www.playgd.mobi Wallet
Herbalism Guide Tbc
Does Iherb Accept Ebt
Theatervoorstellingen in Roosendaal, het complete aanbod.
Ups Store.near Me
Sayre Australian Shepherds
Justina Morley Now
Nz Herald Obituary Notices
Trap Candy Strain Leafly
Mychart Mountainstarhealth
Nc Maxpreps
How To Buy Taylor Swift Tickets By Navigating Ticketek's Stress-Inducing System
Aso Tools Vancouver
Horoskopi Koha
General Format - Purdue OWL® - Purdue University
Omaha World-Herald from Omaha, Nebraska
Craigslist Org Las Vegas Cars
Latest Posts
Article information

Author: Tuan Roob DDS

Last Updated:

Views: 5309

Rating: 4.1 / 5 (42 voted)

Reviews: 89% of readers found this page helpful

Author information

Name: Tuan Roob DDS

Birthday: 1999-11-20

Address: Suite 592 642 Pfannerstill Island, South Keila, LA 74970-3076

Phone: +9617721773649

Job: Marketing Producer

Hobby: Skydiving, Flag Football, Knitting, Running, Lego building, Hunting, Juggling

Introduction: My name is Tuan Roob DDS, I am a friendly, good, energetic, faithful, fantastic, gentle, enchanting person who loves writing and wants to share my knowledge and understanding with you.