Felix Jones

I found a draft of an article I started writing on Medium titled “Enjoying CMake”.

I don’t think enough people defend CMake. There’s a reason it’s ubiquitous in the C++ ecosystem.

There’s some people out there who think we should all migrate to Meson, but I simply don’t see a need. CMake honestly does the job, and Meson to me looks like a project born out of frustration that no-one is willing to talk about CMake’s weird as heck syntax.

Plus Meson has a Python requirement and that puts me off immediately.

I actually like CMake

I like that CMake is actually updated and actively worked on. The new versions tend to add stuff that I find quite useful.

But the syntax is funky

I don’t like the syntax, it’s really goofy. Having to write endif() and endfunction() is weird.

Clearly it was written to be simple to parse, and I guess because of that it is accidentally okay to visually parse. Most of the time.

There’s some legacy nonsense, like when do I ${put my variables in these dollar braces}? When it comes to the if() command it can be inconsistent and surprising.

But syntax isn’t everything, any decent programmer can learn a new programming language as long as they have documentation, which CMake certainly has.

Returning from a function is weird

Particularly trying to return anything at all from a function.

function(do_stuff _result)
  # Do some complex stuff
  set(${_result} "Hello, world!" PARENT_SCOPE)
endfunction()

do_stuff(stuffResult)
message(STATUS ${stuffResult})

So to “return” a value we have to hand a function the name of variable we want to store the value in, and then the function sets the variable of the parent scope from the inner scope.

There is no return() keyword.

Perhaps I find this weird because I’m used to C, which has a rather strict “can only return 1 thing” restriction that CMake avoids through this bizarre pattern.

Parsing function arguments is kind of cool

I have code like this:

set(options
  VERBOSE
)
set(oneValueArgs
  OPTIMIZE
)
set(multiValueArgs
  INPUTS
)
cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

# ARGS_VERBOSE, ARGS_OPTIMIZE, ARGS_INPUTS

This is quite convenient, lets me write some pretty neat CMake APIs.

Why is everything a function???

Everything ends in the call parens (). I’ve seen some crusty, old CMake that uses the closing keywords to add some documentation, but it doesn’t actually do anything, so now we’re all left with parenthesis after every key word.

I got used to it, but it was annoying.

What is the common coding style?

I had to make up my own coding style for CMake. I wish the official documentation had some kind of recommendation.

The style of old CMake is a bit shouty with EVERYTHING BEING UPPER CASE.

But I didn’t always like CMake

My first introduction to CMake was Android. I had no idea what I was doing, I had to Google and copy-paste snippets to get anything done. I could barely figure out how to add header include directories.

That was my CMake experience for a long, long time.

Writing CMake APIs

I think everything changed when I first saw a self-configuring project on the Game Boy Advance, it wasn’t written in CMake, but I liked what it was doing and wanted to figure out a way to get a self-configuring toolchain going and available with familiar IDEs.

CMake is available with familiar IDEs (Meson, is not) so the choice was obvious.

I realised that I could provide GBA specific options to developers making GBA homebrew right there in the build system.

Part of the build process involves running objcopy to copy up all the binary in an ELF file into a final GBA ROM binary.

gba_target_objcopy(myTarget
  OUTPUT myGame.gba
  PAD 256 # Pad the binary to a multiple of 256 bytes
  FIX_HEADER # Apply the header checksum that makes the ROM work on hardware
    TITLE "Awesome Game"
    GAME_CODE "1AGE" # Game identifying code
    MAKE_CODE "FJ" # Made by Felix Jones
    VERSION 123
  ARCHIVE_DOTCODE # Generate eReader dot-codes (lol)
    REGION usa # eReader region
    NAME "My Awesome Game"
)

I am pretty proud of this setup. I think it looks cool, especially if you come from seeing traditional CMake projects which are a wall of set() commands with mysterious variable names.

My CMake complaints

I think my CMake complaints would surprise people, as none of them are related to the language. It’s all about IDE support.

Also, Meson has these same problems.

Tools don’t auto-complete CMake code

I like auto fill suggestions popping up as I type stuff. That would be awesome for CMake. I have all these cool APIs that people can’t discover because no IDE has auto complete for CMake.

In most IDEs I can Ctrl click on something and be taken to its definition, but IDEs seem to not implement this for CMake.

Doxygen for CMake code? Forget it.

CMake has support for Doxygen, but Doxygen doesn’t support producing documentation for build systems. This really sucks, as I struggle to document and communicate to developers what is available.

It’s still WIP??

Here’s a complaint that isn’t tool related: There are CMake features that are still half-finished.

One common action in GBA projects is to bundle game assets in a GBFS archive and append it to the end of your game.

In my CMake toolchain it looks like this:

gba_add_gbfs(myAssets GENERATE_ASM
  "hello.txt"
  "file.txt"
  "image.bin"
  "things.json"
)

# Add the assembly output of myAssets as a source file for myGame
add_executable(myGame main.c "$<TARGET_PROPERTY:assets,ASM_OUTPUT>")

That ASM_OUTPUT is hard coded to myAssets.s, which is rather inflexible if you need to rename the source file to something else (changing its extension, even).

I explain why it’s like this in the comment:

# Ideally this would use $<TARGET_FILE_NAME:${_target}>
# However, generator expressions are not supported in BYPRODUCTS for add_custom_command
# https://gitlab.kitware.com/cmake/cmake/-/issues/12877

Issue created 10 years ago, it has been partially solved (and actually looks like there’s some activity related to it in other issues. Hmmm maybe I should see if this is solved now).

It surprises me that there’s still problems like this.

Where the feck are examples of GOOD CMake projects?

They don’t exist. There’s things I want to do in CMake that apparently no-one has even tried, and whilst the documentation does its best to describe what’s available, something I want to see examples to get inspired.

Quite often I ended up digging in the source code of CMake itself to figure out what’s available and possible with some CMake functions.

My theory: Nobody is proud of their build setups, and why should they be? All the cool stuff is in the thing you’re building, not how you build it.

And because nobody is proud of their build setups, they let them rot into embarressing messes, and then someone else takes a look and says “gee, CMake sucks doesn’t it?”.

A CMake horror story

More than once I have seen this setup:

This is so dumb to me. Clearly what has happened was: “Ugh I hate writing CMake, it is so unreadable and I don’t want to learn the goofy as fuck syntax. I will just write my own Python script that performs all the tasks we need, everyone will enjoy reading my Python code and debugging that”.

CMake has a scripting language: it is CMake. You don’t need Python in your build. That’s 1 less dependency and 1 less barrier of entry to getting people started on your project.

I am still in disbelief that I have encountered multiple projects with this Python dependant setup. Madness.

Oh and of course they use FILE(GLOB MY_SRCS dir/*) and scan for sources, because actively making the CMake experience worse gives you more things to complain about.


In reading this back, I mentioned Python more than once. I have nothing against Python, but too often I see it thrown into a project as some kind of silver bullet for bodging around something that could have quite trivially been solved with the tools already at hand.

Anyway, in conclusion; I’d like people to stop hating on CMake. Thanks.