Contents
Since I have mentioned CMake in a handful of past blog posts, it is time to give a short introduction for those that don’t know it yet.
CMake is one of the most popular build systems for C++ out there. One of the main reasons probably is that it is cross-platform: It does not build the project itself but operates a platform-specific system. That means it can generate Makefiles, ninja-build files, or project files for Visual Studio or Xcode, to name just a few.
This post is the first of a series about CMake:
In this post, I’ll lay out the very first step to approach a CMake-based project. I use MSYS2/MINGW64 on Windows and let CMake generate Makefiles, but the principle is the same for other environments and generators.
Hello CMake!
We’ll start with a very small project: A typical hello world example. It only has a simple main.cpp:
#include <iostream>
int main() {
std::cout << "Hello CMake!\n";
}
Let’s now add an equally simple CMakeLists.txt. That is the file CMake looks for, and it contains the information it needs to know what to build – or, more precisely, what build files to generate. Our CMakeLists.txt, for now, contains only a single line:
add_executable(prog main.cpp)
This line is simply telling CMake that we want to build an executable, named prog
, and it consists of a single source file, main.cpp. You can see this minimal state of our project on GitHub.
Run CMake to generate the build files…
CMake generates a bunch of files in the place where we run it, so it’s good practice to have a separate directory for that. Assuming we’re in the directory where the source file and CMakeLists.txt are located, this is how we can prepare to build the project:
> mkdir _build && cd _build
> cmake .. -G "Unix Makefiles"
The call to cmake
has two parameters: ..
is the path to the CMakeLists.txt, and -G "Unix Makefiles"
tells it to generate a Makefile (try cmake --help
for more generators). The latter will be located in the _build directory where we called cmake
, among other things.
The first time we run cmake
in this directory it will take a few seconds, detecting the C and C++ compilers to use and some information about them:
-- The C compiler identification is GNU 7.2.0
-- The CXX compiler identification is GNU 7.2.0
-- Check for working C compiler: C:/msys64/mingw64/bin/cc.exe
-- Check for working C compiler: C:/msys64/mingw64/bin/cc.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/msys64/mingw64/bin/c++.exe
-- Check for working CXX compiler: C:/msys64/mingw64/bin/c++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
The information CMake finds about the compiler is stored in CMakeCache.txt, one of the generated files, so it does not have to do the chores again every time we run it. If the compiler CMake finds is not the one we want, we can set the environment variables CC
and CXX
to point to the correct C and C++ compilers, respectively. Without those variables, it simply looks for a number of known compilers in the PATH
.
The last lines of the output are about the project itself and will be repeated every time we call cmake
:
-- Configuring done
-- Generating done
-- Build files have been written to: C:/hello_cmake/_build
… and again to build our project
Now that we have generated a Makefile, the last thing to do ist to actually build our program and run it:
> cmake --build .
Scanning dependencies of target prog
[ 50%] Building CXX object CMakeFiles/prog.dir/main.cpp.obj
[100%] Linking CXX executable prog.exe
[100%] Built target prog
> ./prog.exe
Hello CMake!
This call to cmake
directs it to the current directory, and the option --build
tells it to use the generated files to do the actual build. In our example, that is the same as simply calling make
, but it also works for other generators, e.g. Visual Studio project files.
Note that everything created by the build, including the Makefile, the executable, and other build artifacts, is located in our _build directory. That means we can cleanly separate them from our project sources.
Conclusion
This is only the very first step in getting started with CMake. I will add a few more posts about it in the next weeks. I am still learning CMake myself, so the more advanced stuff will come in later posts as I learn it. Come back to the list at the start of this post for a table of contents. The next post in this series is Another Target and the Project.
Please point out any mistakes you see, ask questions, and share your problems and discoveries you had with CMake. Maybe you’d like to write a guest post about it? Get in touch!
Permalink
Permalink
Permalink
I’d say that CMake being cross-platform is the only reason for using it.
When I have the choice, I’m using
tup
(http://gittup.org/tup/) which is fast, powerful, language agnostic and easily scriptable.Permalink
waf
is also a good one, used in prod many years, multi plateform of course, easy to switch between self built libs or linux package libs. MacOS, Linux Windows, 32/64 no pb.https://github.com/waf-project/waf
Permalink
Might I ask which build system you were using before, if any? I’m also learning CMake now and honestly it hasn’t being a good experience.
Searching for alternatives I found Meson and build2 as strong candidates, although right now CMake popularity is much greater than the others options.
So my question really is, why did you choose to learn/use CMake instead of other options? Was it just because of its popularity?
Permalink
Before CMake, I had only used IDE-specific project management, e.g. Visual Studio and Embarcadero, and the occasional Makefile.
The main reason to look into CMake was that it’s the format CLion uses for project definitions. I don’t find it too bad am experience, compared to Makefile and XML-based proprietary project files. If course there are ugly CMakeLists.txt in the wild, since it’s possible to write horrible code in any language 😉
Permalink
Since You use as an example MSYS2/MINGW64, why don’t call cmake with generator option -G “MSYS makefiles” instead of -G “Unix Makefiles”? What is the difference?
Permalink
Great post!!
I always like the simplicity you maintain in your posts.
They are concise, to-the-point and focused.
I don’t know.. just guessing.. are you a teacher?
I really learned few basic facts about CMake here.
I’m recently learning CMake as a total beginner. Personally, I think the biggest problem with it is lack of definitive tutorial. The documentation seems to assume some prerequisite…
Looking forward for next post in this series!
Permalink
Hi Sameer,
thank you so much, I’m glad you like the posts. I am not a teacher, though I have done some tutoring for kids and given some training. I guess by now, I’ve also some practice writing blog posts.
I agree that the documentation is not ideal for learning. I asked my Twitter followers for some material since I’m also still learning CMake, and the best link I got so far is this one: https://github.com/ttroy50/cmake-examples
I hope that (and my future posts) help you learn!
Permalink
Hey Arne, thanks for sharing the amazing link!!
Looks like it has quite detailed explanation for beginners!
Meanwhile, I found this gitlab link with some material:
https://gitlab.kitware.com/cmake/community/wikis/home
I’m also following the two videos from CppCon and C++Now on modern CMake:
https://www.youtube.com/watch?v=bsXLMQ6WgIk
https://www.youtube.com/watch?v=eC9-iRN2b04
Permalink
I don’t like CMake very much. Although I understand it does have some value for cross-platform builds, if you don’t need that feature (which is my case), Gnu-Make is much more convenient. The greatest feature (that does not seem to be implemented in CMake – but I might be wrong) is Make’s ability to have powerful and easy name globbing: once your makefile is done, you can add new sources in a folder, they get build automatically, without the need to touch a single line of the makefile.
Permalink
CMake does have globbing, though some discourage the use. I don’t like GNU make much since the syntax is ready unwieldy imo. That’s ok though, as, to my knowledge, make was designed to be generated, not handwritten.
Permalink
“make was designed to be generated, not handwritten”
I don’t know from where you got that information, but it is certainly not true! Makefile syntax gets back to the end of the 70s’, where there was no such thing as a makefile generator.
But on the syntax, I get your point, as it was the same for me in the beginning. It is pretty hard to understand how it works, when you are used to the classical program paradigm (sequential execution). But once you get it, you understand its incredible power and flexibility.
Permalink
CMake provides out of the box :
– Automatic dependency management between compilation targets
– Out of the source tree building
– Easily finding libraries and dependencies
Also, CMake is simpler to use and maintain than a Makefile.
Permalink
I don’t find cmake intuitive at all and the syntax overly verbose. After not using it for a couple of weeks, I always begin to forget even essential elements and have to look them up again. Parsing and understanding cmake project files written by others often caused a stackoverflow in my brain. Not so with Qbs. The pseudodeclarative QML language is surprisingly easy, consistent and efficient. Qbs is very performant. I never want to touch makefiles and makefile generators again in my spare time. Life is too short.
But that’s just my experience and opinion. Given the popularity of cmake, that seems to be no issue for the majority of developers.
Permalink
From what I know, most developers who use CMake don’t really like it either. For many it seems to be the lesser of a selection of evils.
Permalink
CMake is far from being perfect but unfortunately C++ cross compile build systems are big pain (including boost build).
Permalink
This is great for beginners. Thank you very much for your time!
Permalink
Great intro. After lots of messing with different generators, I’ve come to prefer
cmake --build .
to invoke the build tool after generating. If nothing else, it maintains symmetry with the generation command, and it even knows how to do command line builds for MSVC (for example), which is not always obvious.Permalink
Yes, thank you – I should have used that. Will add that info soon!