
An Introduction to Modern CMake - uyoakaoma
https://cliutils.gitlab.io/modern-cmake/
======
ahartmetz
Somehow I'm never happy with CMake guides. The official book is ridiculously
outdated among other things, the official documentation is complete and
current but provides no guidance, and none of the blog-documentation about it
gives a complete picture. Some of it is bad advice. This one doesn't talk
about the dependency graph, something I consider very important in a build
system. It is also missing a lot of other stuff. (Fair enough, it's provided
for free.)

I wrote the CMake training material for KDAB (www.kdab.com) while I worked
there. In my biased opinion it's the best CMake guide around, especially after
it was extended and polished by coworkers :). I tried to mix explaining the
"inner logic" of a build system, and CMake in particular, with examples,
resulting in something I'm reasonably happy with. The main difficulty was
ordering the topics to avoid too many forward-references while also not
starting with a lot of not-obviously-interesting groundwork [1]. (I don't work
there anymore btw, so the interest I have is getting my work used)

This intro to modern CMake should be good because Steve is a major CMake
contributor and good at explaining. The presentation even contains a
dependency graph! [https://steveire.wordpress.com/2017/11/05/embracing-
modern-c...](https://steveire.wordpress.com/2017/11/05/embracing-modern-
cmake/)

[1] Aside: I figured that "There are two types of bad software documentation:
math textbooks and cooking recipes. The former does not explain why you are
doing things and other helpful context, the latter won't help you if you need
to do something differently, which is almost always."

~~~
cbHXBY1D
The official documentation is sub-par. Want to know the difference between
include_directories and target_include_directories? Well, good luck going
through docs with no examples and then parsing through several year old
stackoverflow posts.

~~~
ahartmetz
That is what I mean with "no guidance". In this case, you have a common best
practices question that you have to figure out by diffing the two pieces of
documentation yourself. The information is usually there.

------
tux1968
The recommended compatibility boilerplate for new projects is tedious. There
must be a way to include this knowledge in cmake itself rather than requiring
every "properly" configured project to get this right:

    
    
      cmake_minimum_required(VERSION 3.1)
    
      if(${CMAKE_VERSION} VERSION_LESS 3.12)
        cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
      else()
        cmake_policy(VERSION 3.12)
      endif()
    

This is all before you even start thinking about your own project.

~~~
baby
This syntax is also uber ugly. I can’t understand why C still hasn’t a proper
build system that is either using convention over configuration (like Go or
Rust) or something like cmake but with a proper syntax (maybe something in
pyhon?)

~~~
bendmorris
Scons ([https://scons.org/](https://scons.org/)) is one alternative. The
configurations (and Scons itself) are Python.

~~~
electricslpnsld
Scons has soooo many problems. You still can't pass in positional linker flags
without completely hacking how it calls the linker. :(

------
overgard
It’s better than every other build system, but god I wish they had just used a
preexisting language for it. The syntax for CMake just feels so janky and
weird, and it’s another set of things I have to remember and (eventually)
forget. It’s not like it has these amazing language concepts nothing else can
do; and if they’re worried about size or dependencies something like Lua would
add like no overhead and be super simple to link.

~~~
DoofusOfDeath
I agree. CMake's scripting language has a lot of the same pain-points as Bash.
For example, the lack of a clear distinction between lists and strings.

I've sometimes wondered if CMake would benefit from getting an additional /
replacement scripting language. While retaining the same underlying object
model and other code, that is.

My only fear would be that it would be a first step in morphing into Bazel,
which I find unbearably complicated.

~~~
DoofusOfDeath
Clarification: I know that Bash has arrays. But environment variables such as
"PATH" use colon-separated strings.

------
superkuh
The problem with modern cmake is that it _cannot_ run on older systems. So if
you want to use it you end up either static compiling an incredibly hard and
tedious depedency tree or have to use some just released OS.

What I want out of a make system isn't bleeding edge features. I want to be
able to use it for more than 3 years.

~~~
IshKebab
A nice tip is that you can install a very recent CMake from pip with most
systems.

Just `pip install cmake` and then you can require version 3.12 and don't have
to worry about ancient Ubuntu packages or whatever.

~~~
superkuh
On systems were you don't need to do this (new enough) it'll work. On systems
where you _need_ to do this it will break things. And because it's pip, it'll
be incredibly hard to figure out what broke and how to fix it.

~~~
geofft
In general you can avoid pip breaking things by doing so inside a virtualenv.
The following should work on at least the last few years' worth of OSes:

    
    
        $ virtualenv /tmp/ve
        $ /tmp/ve/bin/pip install -U pip setuptools
        $ /tmp/ve/bin/pip install cmake
        $ /tmp/ve/bin/cmake ...
        $ rm -rf /tmp/ve
    

On some OSes (e.g. Debian and derivatives) you'll need to install virtualenv
itself from the OS first, but that won't break things because it's from the
OS.

On sufficiently old OSes, you may need to set
PIP_INDEX_URL=[https://pypi.org/simple/](https://pypi.org/simple/) and
PIP_TRUSTED_HOST="pypi.org files.pythonhosted.org" to disable certificate
verification. (I'm not sure of a good way to work around this problem. In
theory, Python 3 would solve it, but those same old OSes have an old enough
Python 3 that the latest version of setuptools fails, and I can't figure out
how to install an old enough setuptools.)

Also - if you need to unbreak your system Python, in _general_ it suffices to
ensure that /usr/local/lib and ~/.local/lib have no pythonX.Y directories with
anything in them. (Empty directories are fine.) At my last job where we needed
to give non-sysadmins root access on certain machines, I added a Nagios check
to /usr/local/lib/python*, which was remarkably effective at catching problems
before they turned inexplicable.

------
mr337
This is quite welcoming. I have tried to digest cmake docs and get so lost.

Am I the only one that can’t grok cmake docs?

Edit: spelling fixes

~~~
lilott8
The only way I've been able to make any sense of CMake docs is in conjunction
with examples -- searching on Github/enormous open source projects using it
(e.g. LLVM). Just reading CMake docs to understand how to use directive x
almost always leads to failure.

~~~
mr337
Yup agree, I’m like I know this X project compiles file, let me Look at their
cmake file....copy pasta.

I’m not proud of it, but it works.

------
MrQuincle
What I find a big shortcoming from CMake is that it does not have support for
building for multiple architectures at once.

[https://cmake.org/pipermail/cmake-
developers/2014-September/...](https://cmake.org/pipermail/cmake-
developers/2014-September/022929.html)

Quote: "The fundamental problem with supporting multiple architectures is that
pretty much all of CMake is designed to support one architecture at a time.
Modules/*, CMakeCache.txt, etc. are all built around only finding, using, and
building one artifact per library (OS X universal binaries work with multiple
architectures because they are still only one file). I think even your
"toolchain scope" approach would end up being used in practice to wrap the
entire CMakeLists.txt file."

~~~
simonask
CMake replaces `configure` scripts. It would be difficult to imagine what
multi-architecture support in a single build directory / command-line
invocation would look like.

Instead, what we do is to wrap calls to CMake in a script that makes choices
about build directories, which flavours to build by default, which mobile SDKs
exist, etc. When producing a release, we notably don't use this, because we
are precisely interested in building for each architecture in parallel on
different machines.

~~~
MrQuincle
Yes, for cross-compiling to targets with different configurations we also
evoke CMake with lots of options.

However, that you have to wrap calls to CMake in your scripts is quite ugly.
Are those scripts cross-platform for starters? As soon as you start to write
code to use CMake, this seem to defeat the purpose of a build generator.

~~~
amrox
OpenCV includes a python script to build a framework for iOS. I agree, it’s
not great.

------
nwmcsween
Please don't use cmake, the dependency chain it pulls in on a bare machine is
massive.

~~~
nerdponx
What's wrong with dependencies?

~~~
gumby
Every dependency increases the fragility of your program. What if you update
the dependency and it breaks your program? What if you have two programs
dependent on the same library -- but different versions? This just scratches
at the surface of the problem.

Sometimes the risk is worth it: you need some complex functionality not worth
writing yourself. In that case it's a good thing. But understand that it's a
tradeoff.

~~~
hugofirth
This is a very dangerous argument for anything but the most domain specific or
simple logic. Every time I roll something non trivial rather than using the
widely testing and "battle hardened" alternative _that_ increases the
fragility of my program.

There are times when it makes sense, but those are the special cases, and
carry a cost which should be considered.

------
gjasny
I could also recommend Craig Scott's recent CMake book: "Professional CMake":
[https://crascit.com/professional-cmake/](https://crascit.com/professional-
cmake/)

IMHO it's the best available CMake book available right now. I especially
liked the recommendations at the end of every chapter.

------
Sir_Cmpwn
I think the best advice I can give to someone learning about cmake is to use
meson instead. I once had dozens of codebases using cmake, and have since
moved most of them to meson and start most new new projects with meson.

~~~
nwmcsween
I dislike meson, it mixes configuration with generation when a separate
project (if even really needed as sh is fine) for configuration could exist.

~~~
wirrbel
What?

~~~
nwmcsween
It mixes configuration as in ./configure related with generation as in build
generation.

------
ur-whale
CMake is the perl of build systems: useful and feature-fat but one of the
worse DSL syntax I've had to grapple with, barely better than that of a
Makefile

~~~
enriquto
The CMake language is much, much worse than the beautiful makefile syntax.

~~~
ur-whale
lol, calling the makefile syntax beautiful is quite a stretch, but maybe
having been exposed to enough cmake code slowly rewires your brain to the
point where that can happen :)

------
quietbritishjim
This is really not an introduction to CMake. It is more like a collection of
the author's opinions about CMake, barely categorised into sections and some
quite contentious.

An introduction would be example driven, starting with a very short but
complete example:

    
    
        cmake_minimum_required(VERSION 3.1)
        project(FooAndBar)
        add_executable(Foo foo.cpp)
        add_executable(Bar bar.cpp)
    

It would explain projects and targets (noting the different meaning of
“project” to some IDEs including Visual Studio: project<->solution and
target<->project). Then you would progressively build from there. Start by
adding a dependency such as protobuf to show find_package() and
target_link_libraries(), showing both the new protobuf::libprotobuf target
dependency and the old-style ${Protobuf_INCLUDE_DIRS} and
${Protobuf_LIBRARIES} variables. Then make some shared code between your two
executables into your own library, discussing shared vs static. I would not
even mention variables until after this point (even though
${Protobuf_INCLUDE_DIRS} already is a variable).

In other words, an introduction should be top-down and pedagogical. This is
the exact opposite of that: picking through a CMakeLists.txt from the bottom
up, one line at a time, before you even know what the point of it is.

Perhaps I misread the title: I read it as “introduction to CMake [but using
modern techniques]”, but maybe it’s “introduction to the modern bits of CMake
[assuming you already know the old stuff of CMake]”. But even that could be
example driven, admittedly with more effort: start with an old crusty
CMakeLists.txt with plenty of bad habits and make it better one step at a
time. Or have lots of little CMakeLists.txt with one bad habit at a time, and
fix each of those.

I am not convinced by some of the recommendations it makes either, although I
think there will always be some disagreement about some of these things. I
have already given my view on the “cmake_policy” atrocity (which is the very
first thing in “Introduction to the basics”!) in another comment here. And in
the examples there are workarounds for old versions of CMake that don’t have
targets for e.g. find_package(boost) by creating interface targets using the
old _LIBRARIES and _INCLUDE_DIR variables. These are very neat but not very
accessible to CMake beginners. It would be much simpler to put up with using
the old variables, or commit to increasing your minimum supported CMake and
forget them entirely.

------
codedokode
I used CMake and I liked that it can produce NMake files, which can be used
for building with Windows SDK (not bloated Visual Studio, SDK contains just
headers and compiler) on Windows XP.

And generally it is much easier to use than manually write Makefiles or use
Autotools with weird unintuitive syntax. As I remember, they use `dnl` keyword
(download?) for comments!

~~~
ur-whale
You like CMake in much the same way people like Visual Basic: it has lots and
lots of features and therefore people find it useful.

But, just like VB isn't a good solution for an embedded language, it doesn't
mean cmake is a good solution to the general problem of building large
codebases.

~~~
codedokode
No, I like CMake because it is better than writing Makefiles manually or using
autotools.

------
DoofusOfDeath
One of my problems with "modern" CMake is the same as my problem with "modern"
C++:

Their proponents argue for using certain new language features to get things
done. But because of backwards-compatibility concerns, the system still
supports the old, quasi-deprecated constructs for doing those same things.

And so you end up with an overly complicated language that seems to have
multiple, reasonable ways to do the same thing. And codebases containing a mix
of the two, even for newly written code.

IMO it would be better for CMake to make a clean break, and have CMake 4
_only_ support "modern" CMake.

(edit: And, as I've posted elsewhere, switch to a more robust scripting
language.)

------
kevinoid
There are some good recommendations in here! Does anyone know of a way to
check for, or enforce, these recommendations? The only CMake linters I could
find seem to focus on whitespace and naming issues.

------
khazhoux
Can anyone with experience with Bazel, Buck, or Pants chime in?

~~~
Game_Ender
They are quite different, speaking of Bazel it’s designed to be an all
inclusive build system for any language at essentially infinite scale. Which
means it:

\- Has nice Python based DSL

\- Strongly encourages 100% explicit dependency specification

\- Has built in caching (local and remote)

\- Built in test result caching

\- a fully featured build graph query language

\- Built in distributed execution support (for any build step)

\- All work scales by part of tree you are building not it’s absolute size

\- Support for fetching external source code and binary deps

\- Has first class support for executing and testing containers

\- Has first class support for code gen (ie you can build a compiler use it to
generate code, then build its output,and everything works, no hacks in one
build system)

The downsides:

\- Works best if you all your code is built with Bazel, which makes deps hard

\- Support for Python is weak, Ruby non existent, and Node is beta quality

\- Has essentially zero convention, everything must be explicitly configured
(ie explicitly describing go deps)

\- Has a memory hungry local java daemon

\- Has a more overhead than ninja (but is more accurate because of hashing and
isolation)

~~~
ecnahc515
One downside I found: it makes heavy use of symlinking which doesn't always
play well with other tools (in particular: Go tools) and I found made things
particularly annoying when I used Bazel from inside docker and outside docker
(the symlinks left don't match what's on my host).

