The ugly syntax is mostly a maintenance issue, but the real problem is that CMake doesn’t expose all of the internal build system “machinery” that you need to build something complex or performant.
Take waf for example: it’s effectively a Python framework for creating a custom build system, with every single internal feature accessible (and documented) with good design instead of abstractions. In waf, you can do things like:
* define custom build Tasks
* have those Tasks spawn other Tasks dynamically (e.g. by parsing the stdout of a compiler to find output files)
* Extend/modify the behavior of existing Task classes (e.g. the C/C++ linker)
* Modify the task scheduler to control execution order
* Implement commands that analyze your build scripts (e.g. to generate a compile_commands.json file, or anything else)
* cache any serializable Python types between build invications
* create reusable project-agnostic build scripts
* wrap everything in a nice API so that build scripts are manageable
* rich debugging features
Not to mention the expressive syntax of Python and the full power of the standard library.
And all you need is a Python interpreter (2.7 and 3+ both work), as Waf has zero dependencies.
The learning curve is high, but it’s worth it for projects that have a lot of build complexity. In my case, I use CMake until a project needs custom functions, or needs to support multiple languages, then I switch to waf.
Say your program requires some static data stored as XML files that link to each other (e.g. XLink), you need to convert them into a different in-memory representation, and then compile them into an object file for embedding into the binary, and maybe generate some header files to access the data. Maybe you even want to compress the data at build time, and link with zstd to decompress at runtime if the user passes a `--compress-data` flag when building.
In waf, it's possible to implement everything you need for that in a highly efficient way: the 'scanner' method that parses XML files to find linked resources to setup dependencies, the task that generates the header file with optional decompression API, etc. Because the task scheduler is flexible and programmable, you can implement it so that nothing runs unless it absolutely has to. Even the dependency scanner can cache data between builds so that it doesn't need to re-scan for dependencies unless any of the files it found in a previous invocation changed (hash, timestamp, etc)
For example: MyData.xml links to CoolStuff.xml; user modifies the whitespace of CoolStuff.xml, so MyData.xml needs to be rebuilt; the Task that converts it into a new representation runs, but since whitespace didn't affect the output of that task, the output file's hash is identical to what it was before, so the other tasks don't detect a change, and the build ends immediately.
In CMake, it would be impossible to implement something comparable. With custom functions/external scripts you can technically do anything, but there's a lot of inefficiency both in runtime and development effort. Since CMake doesn't actually perform builds itself, it's limited in what you can directly do, and the DSL isn't powerful enough to take advantage of more flexible CMake internals even if they existed.
But if you only need to compile C and C++ files with some light scripting, CMake is far superior for a lot of reasons.
Take waf for example: it’s effectively a Python framework for creating a custom build system, with every single internal feature accessible (and documented) with good design instead of abstractions. In waf, you can do things like:
* define custom build Tasks
* have those Tasks spawn other Tasks dynamically (e.g. by parsing the stdout of a compiler to find output files)
* Extend/modify the behavior of existing Task classes (e.g. the C/C++ linker)
* Modify the task scheduler to control execution order
* Implement commands that analyze your build scripts (e.g. to generate a compile_commands.json file, or anything else)
* cache any serializable Python types between build invications
* create reusable project-agnostic build scripts
* wrap everything in a nice API so that build scripts are manageable
* rich debugging features
Not to mention the expressive syntax of Python and the full power of the standard library.
And all you need is a Python interpreter (2.7 and 3+ both work), as Waf has zero dependencies.
The learning curve is high, but it’s worth it for projects that have a lot of build complexity. In my case, I use CMake until a project needs custom functions, or needs to support multiple languages, then I switch to waf.