CMake is awful

CMake is awful. It's awful enough that writing about how awful CMake is is a stereotyped blog entry. It's so awful that I feel compelled to write about how awful it is, even knowing how unoriginal that is. I need catharsis.

CMake is bad at its job. It has a bad job, and it does it badly. In some ways it has many jobs, but the bit that matters is the bit that gets in your face. CMake is a dependency finder.

A decade ago, when I last used it in anger, it was a build file generator. We used it to generate build files for our project that would work on a bunch of different platforms with their various build systems. It was boring, it did the job. This is not a hard job: Your code is supposed to work together, making it do so is not so hard.

It turns out the tricky bit is building disparate stuff together: Taking random libraries etc. and gluing them into a coherent whole. CMake's real-world role is to make your dependencies work together.

It's a miserable job. This isn't even dependency management in the sense of package management: It doesn't have the authority or responsibility to own installations and make things work together. Oh no, it just has to scrape around your system, deal with what's provided, trying to find the dependencies, and staple them together.

The canonical failure mode of CMake is that you have a dependency installed on your system, and it cannot find it. You can see it there, sitting in the file system, while CMake's just refusing to see it. It's embarassing.

No human wants to care about this. When a piece of software breaks, I want to debug it. To debug, I need to build. And to build, I need the dependencies to work. I hardly want to care about installing dependencies. Why on earth should I care about getting CMake to recognise them?

No sensible human being, tasked with making software work, wants to have to care about the details of how CMake finds things. The fundamental problem of CMake is that it makes you become an expert in something you really couldn't care less about.

CMake, along with other tools that most people use glancingly and couldn't care less about, should follow two simple rules: 1) Work 2) When you don't work, be really, really easy to debug, so we can stop breaking rule #1.

CMake breaks this rule most egregiously. Old-school Make is... actually not that bad at being diagnosable. By default it has a relatively straightforward approach, and there are decent-enough tracing flags. Make is decades old, and gets this right.

Let's compare with CMake. In an ideal world, there would be a simple, obvious flag that puts it into a diagnostic trace mode, allowing you to see what happens. If it fails, it would explain in detail how it failed and/or explain how to get more info. CMake does not do that. Instead:

  • Default output is uninformative. You need to set '--trace' to see what it's up to. Ha ha. Only joking. '--trace' doesn't expand variables before logging, so you get entirely useless lists of entries like "if(NOT TARGET ${_target} )". You need '--trace-expand' to get useful information. This is positively user-hostile and gives you a good idea of the mindset involved.
  • This trace only tells you what it's executing, with no real clue to telling you how it got there. Fortunately, the mechanism for finding dependencies is clear and explicit. Ha ha. Only joking. As documented, attempts to find dependencies are automagical, using at least 3 different mechanisms, and generally involving searching for magically-named files in various search paths.
  • To work out what it's finding where, you need a different flag: --debug-find. Because, as I said, if you're fixing an issue you don't care about, you want to become an expert in the various different flags.
  • Except... as far as a tracing tool goes, it makes the classic error of helping you understand what succeeded, not what failed. It tells you what files it found, and you have to search for the failure-shaped holes yourself. It does not tell you what it tried, so you don't really know why it didn't find your dependency.
  • And none of this really helps the fact that it does not make it easy to fix the problem! What I want to do is just put the path explicitly in some simple file, have CMake believe that the thing is there, and get on with my life. As it is, the correct solution appears to be to learn the language, learn how to specify the components of a library, create a FindWhateverLibraryImLookingFor.cmake file, and then trick CMake into discovering and using said file.

In short, it's a user-hostile piece of software. It does not care about the use case of "This thing isn't working. How do I easily make it work?". It... feels like the kind of person who thinks C++ templates are good because they're subtle and complicated and tricky, and thus make them feel smart that they understand them. It piles up accidental complexity and assumes its essential complexity. It's utterly awful.

In many ways, you can tell it's going to be bad when the standard usage recommendation is "mkdir build; cd build; cmake ..". Baked in, at the very first level, is that the obvious way to use it is wrong. It's a warning for all that follows.

Posted 2023-04-26.