CMake Rants

CMake is a highly popular ‘meta-build’ system: it is a custom declarative syntax that is used to generate build scripts for all major OSs and compilers (e.g., VS solutions and projects). Designing such a system is a formidable task, but really not a very wise one to undertake in the first place.

I know there are only languages that people complain about and languages nobody uses. I know it’s bad manners to complain about stuff you get for free. I also know I’m working on windows and CMake scripts are generally authored by people who don’t care much about windows development.

And still I can’t help it. Here are a few particular irks.

Terminology

When CMake asks ‘where to build the binaries’ it isn’t talking about anything resembling a binary. It’s asking about the proper location for the outputs of its processing – i.e., solutions and projects (or other build scripts).

UI

This goes beyond a simple ‘designed by an engineer’ cliché. How long did it take you to figure out you need to repeatedly click the ‘configure’ button until all red lines are gone, then ‘Generate’?

Syntax

Not so good.

Clean/Rebuild

..is generally just a recommendation.

But what really makes CMake nearly unusable to me is –

CMake’s treatment of paths

In all generates build scripts, paths are absolute. In vcxproj’s – Output path, Additional Include Directories, PDB path, Linker input directories, custom build steps in ZERO_CHECK and ALL_BUILD, etc. etc. – are all absolute paths.

This makes CMake-generated projects almost useless: you cannot source control them or share them in any other way.

Turns out this has been known for quite some time. There’s a macro called CMAKE_USE_RELATIVE_PATHS but its documentation says:

May not work!… In general, it is not possible to move CMake generated makefiles to a different location regardless of the value of this variable.

It seems they tried to fix it for a while but gave up, and instead posted a FAQ which I don’t really understand:

CMake uses full paths because:

  1. configured header files may have full paths in them, and moving those files without re-configuring would cause upredictable behavior.
  2. because cmake supports out of source builds, if custom commands used relative paths to the source tree, they would not work when they are run in the build tree because the current directory would be incorrect.
  3. on Unix systems rpaths might be built into executables so they can find shared libraries at run time. If the build tree is moved old executables may use the old shared libraries, and not the new ones.

Can the build tree be copied or moved?

The short answer is NO. The reason is because full paths are used in CMake, see above. The main problem is that cmake would need to detect when the binary tree has been moved and rerun. Often when people want to move a binary tree it is so that they can distribute it to other users who may not have cmake in which case this would not work even if cmake would detect the move.

The workaround is to create a new build tree without copying or moving the old one.

The way I see it the real reasons for this sorry state are laid out in this 2009 discussion:

You should give up on CMAKE_USE_RELATIVE_PATHS , and we should deprecate it from CMake.  It just does not work, and frustrates people.

… It is really hard to make everything work with relative paths, and you don’t get that much out of it, except lots of maintenance issues and corner cases that do not work.

An alternative that I’m growing fond of

Is ‘Project from existing code’:

Download the sources you wish to use, and instead of invoking CMake on the root CMakeLists.txt, invoke ‘Project from existing’ code, select the source folder and follow the rest of the wizard instructions.

Today this approach worked for me perfectly on the first try (on this library that is admittedly simple in structure), but that was just lucky. It certainly isn’t perfect – but it is simple, and the generated project files do use relative paths. I’m beginning to think even when tweaks are needed, this is a better starting point for a usable project. Do tell me in the comments if your experience is different.


Edit:

It seems some words of clarification about my usage scenario are in order.

I wish to import an open source project to my build environment, and continue from there. With my build environment. Is that such an exceptional scenario? (It might be, judging by the comments below). I was under the impression that this is what CMake authors aimed for (why generate a VS project/solution otherwise?), and if I was creating an open source package that is how I would want others would use my code.

Moreover, except in the simplest of cases this is the only way to go: a CMake-generated project cannot possibly be the final say. All native build engines have their special knobs and handles that often must be tweaked. Did you ever, e.g., want to change the import library for a dll? Not really possible in CMake. Not to mention more advanced stuff – e.g., rebase it or make it ASLR.  I didn’t mention it above because I don’t consider this a CMake flaw – it’s just too much to ask of a portable build system. All you can expect from it is to set a portable common ground.

So I would expect CMake to generate a native build package (say, VS solution) in a way that would make it possible to forget it was generated by CMake.   Due to all the native knobs and handles this is an inherently hard user story to implement – but CMake fails much, much earlier. I’d consider using relative paths a must-have, and I don’t see why portability makes this task any harder than, say MSBuild authors’ task of using relative paths.

This entry was posted in C++. Bookmark the permalink.

19 Responses to CMake Rants

  1. Mark Final says:

    Good article. Addresses many of the struggles I had with CMake, among others.

    But yes, I’m responsible for one of those new standards as mentioned above, for trying to gradually improve on existing build systems. 1) Make it open source for everyone to use and improve, 2) base it on a real programming language so it can be debugged and profiled by developers, 3) make it fast, 4) make it straightforward to use and understand, 5) make it work for as many projects as possible, 6) provide lots of examples, 7) make it extensible, 8) provide project generation so everyone can use their favourite IDE.

    It’s still work in progress, but http://buildamation.com is where to go to try it out.

    • Ofek Shilon says:

      Thanks. It’s actually not my choice to make – this rant relates to importing an open source github project to our environment, and they chose CMake (as most do).

  2. Andreas Weis says:

    Allow me to counter-rant a bit. :)

    Terminology – This is only partly true. The generated files contain information about where the resulting binaries will be placed. Most of this information can be parametrized through CMake. There are some limitations (placing binaries outside the build tree is usually a bad idea), but claiming that CMake simply does not offer control about this is a bit unfair.

    UI – If you have to configure more than once before you can generate, I would consider that a bug in the CMake script. It is a bit sad that CMake allows this in the first place, but there is absolutely no reason to let lazy build system maintainers get away with it.

    Syntax – Yes, it’s not nice. As with all languages that have crappy syntax, this is mostly due to historical reasons. Not sure if this is reason enough to throw the whole thing out the window. But yeah, improvements would be nice. Suggestions are welcome.

    Clean/Rebuild – Is surprisingly hard. This is one of those issues that seem trivial, but somehow it’s still a huge mess in all of the existing tools. Not sure why, but I don’t feel very confident that I could do better if I were to start from scratch.

    Paths – My answer would be, don’t distribute generated projects. Just check in the CMake source and have your users run CMake on their own. I consider anything that falls out of the CMake build process before the install step potentially non-portable. I see why this might not be acceptable for people. In particular, it forces your users to gain some proficiency with CMake themselves if they want to build your project (which is good for CMake, but maybe not good for your project).
    I guess in the end this is a trade-off. Changing CMake to generate portable project files is not easy and introduces a ton of issues which don’t have obvious answers (the question of how to deal with external dependencies in such a case seems unsolvable to me at first glance). Because of this, the critical mass of users required to give this feature enough weight to be considered for implementation is very high. I am doubtful whether you can reach this critical mass, given that this probably only becomes relevant in a somewhat narrow use case.

    Project From Existing Code – Glad that it works for you. I personally switched to CMake because I needed a unified build that works across multiple platforms and build tools. I cursed at CMake many times for its numerous quirks and challenging learning curve. However, to this day I simply have not found any other tool that solves this problem.

    • Ofek Shilon says:

      Thanks. You actually seem to agree with (most of?) my points, but claim – perhaps rightfully – that that’s the best we currently have. Can’t argue with that.
      Specifically regarding paths: I wish to import an open source project to my build environment, and continue from there. With my build environment. Is that such an exceptional scenario? (It might be, judging by the comments here). I was under the impression that this is what CMake authors aimed for (why generate a VS project/solution otherwise), and if I was creating an open source package that is how I was hoping others would use my code. Undoubtedly this is an extremely hard user story to implement – each build environment has many non-portable knobs and switches – but CMake fails much, much earlier. I’d consider using relative paths a must-have, and I don’t see why portability makes this task any harder than, say MSBuild authors’ task of using relative paths.

  3. Anonymous says:

    “This makes CMake-generated projects almost useless: you cannot source control them or share them in any other way.” – CMake-generated stuff is not supposed to be source-controlled, but to reside in the build directory with all the build files. This makes it a bit misleading, but you should treat generated VS projects as generated Makefiles – they exist only to run “make” on them (or, to run Visual Studio in this case). It is Visual Studios’ problem that build instructions an project information for IDE are merged.

    I agree that CMake is far from ideal, but it is the only existing IDE-independent build system that is capable of really large C++ projects with loads of nontrivial dependencies and build rules.

    • Ofek Shilon says:

      Mostly rephrasing my reply @Andreas Weis (it was over a minute ago!! How come you didn’t read it?):

      Except in the simplest of cases, a CMake-generated project cannot possibly be the final say – as all native build engines have their special tweaks that often must be tweaked. Did you ever, e.g., want to change the import library for a dll? Not really possible in CMake (http://stackoverflow.com/questions/34575066/how-to-prevent-cmake-from-issuing-implib). Not to mention more advanced stuff – e.g., rebase it or make it ASLR.

      So I – and I suspect many others – wish to import a CMake generated project to my build environment, and continue from there. With my build environment. The multitude of native tweaks makes this a hard user story to implement – but CMake fails much, much earlier. I’d consider using relative paths a must-have, and I don’t see why portability makes this task any harder than, say MSBuild authors’ task of using relative paths.

  4. markjamesabraham says:

    Seeking to store generated artefacts in source control is a classic anti-pattern. In your case, it’s guaranteed useless to anybody else, even you on a different machine. Store only the CMakeLists.txt and run cmake when you start to build the oroject

    • Ofek Shilon says:

      Thanks. It seems my scenario isn’t the obviously default scenario (see the previous replies) – I’ll edit the post to reflect that.

  5. CMake is great whenever you have to deal with a highly heterogenous development environment. It is perfect for open source development and for library vendors. On the other hand, for developers that work in a corporate environment where the product is only ever compiled in-house, the cross platform focus of CMake is less applicable. It is not uncommon for
    development environments to be strictly controlled and homogeneous, particularly so when technologies such as Docker or some other form of build environment virtualisation has been adopted. In this environment, CMake has less to offer, and there may exist alternatives that do what you want with fewer drawbacks. CMake is a good tool that does a particular job very well. The issues come when we apply the Peter Principle and start to use it to do jobs for which it was not designed. As always, the person who knows your own problem best is you. So, YMMV.

  6. petermtate says:

    I have just gone through the same pain, integrating a CMake open source project into our internal Visual Studio based build system. CMake is the worst cross platform build system, except for all the other ones.

    I ended up taking a different approach, run CMake, run the build (since the build generated some additional source files), run a script to convert the full paths to relative paths (even more fun since CMake sometimes used windows style back slashes, and sometimes unix style forward slashes). Strip out custom build steps (which would detect changes and re-run CMake again!). And then delete *.tlog files which Visual Studio uses to figure out that the generated source files should be deleted when you run ‘Clean’ on the build.

    I was a huge pain to figure this all out. I sympathize with these library developers, however, cross platform builds are a really hard problem, and I can understand that making it easier for you and me to fork their library is not huge on their priority list.

  7. Dave McMordie says:

    Thanks for articulating the frustrations we all share with CMake!

    CMake is 99% useful, which makes it 100% a frustrating waste of time for new projects, and just something we have to deal with for stuff on github. The biggest issue is this relative path thing. Instead of preaching to the user community and telling us we should just regenerate projects when we move code, they should probably listen and acknowledge this is a critical feature which was missed early on in the design and is now going to be very costly to fix.

    A project generator where a simple path rename breaks the build is kind of hopeless…

    • Ofek Shilon says:

      @Dave thanks. I’m afraid CMake’s current momentum makes it unlikely that any new cross platform build system would replace it, so our best hope is to get someone in kitware to rethink the path issue priority. Do you think that’s possible?

      • Dave McMordie says:

        I was actually wondering how hard it would be to fix; I spent some time digging into this today. It seems to be bound up in some cases with the windows path length limit. We have asked one of our developers to dig deeper and provide an estimate.

  8. Anonymous says:

    You do not share vcproj files with your team. You share CMakeLists. That’s the spirit!
    Also, syntax sucks (borderline capricious!)- to be blunt about it- but once one learns what to avoid (a couple of years of trial and error anguish) everything is a smooth ride!
    The real problem with CMake is that it evolved and is now bound to earlier paradigms (the 7 zones of “policies” ‘s in Dante’s hell).
    They should rewrite it with the newest ideas, call it cmake2 and provide with a huge service to everyone, because it is really useful.

  9. MadMartian says:

    I think a build system that generates another legacy build system (makefiles) is brittle and dangerous if not convoluted. It feels like CMake was conceived to address shortcomings with makefiles while being compatible with it. After 28 years of engineering experience I have learned that it is better to throw away old crap and old garbage and build something completely brand new and independent of the old rather than attempting to compensate for the errors of the past.

  10. Anonymous says:

    cmake is a hilarious cludge (today) in which most of the industry is using it because there is nothing better. It won out. I want to $!^& every single cmake zealot who tells me “once you get used to it, it’s great.” If people adopt this mindset (which majority do), then progress on a (new) build system will never be made. We should strive for making new build systems that take away the pain for NEWCOMERS, not learning to live with our existing GARBAGE. Yes, cmake IS garbage. FACT. It did what it was supposed to 20 years ago and people came along the way, saw issues developing and tried to make new build systems. Unfortunately, they never won, because by that point people were already complacent with cmake
    .
    There exist a few build systems with more modern ideas; however, most turn out to be toys or don’t scale well or have that one fatal flaw that keeps the idea from flying. Tup is a good example. Monitoring the file system in a server like scheme is a cool concept and allows for ultra fast incremental builds. However, the fatal flaw is that it relies on OS specific hooks to accomplish that functionality, which could fail to work in the next version of the OS. Additionally, when tup was conceived, mainstream blazing fast solid state drives and multi core CPUs were not wholly the norm yet. Granted, I think it was around 2015, but come on.. Even the majority of medium to largish size projects running on main stream developer CPUs and solid state drives will take only a second or two to scan a directory structure of thousands of files. Computing speeds will continue to increase, but the number of files in projects will not increase at a matching rate. Basically, cool concept, but not really valid for today’s computing speeds of scanning a massive directory structure, therefore why bother with the OS specific hacks to make it work? And trust me, they are hacks. I work on windows exclusively and the features I needed were broken due to the fact tup is using dll injection on windows.

    When did a build system stop being about actually building the goddamn code in a sane manner and become all this dependency management garbo? When projects got more complicated? How about this idea for size. How about we make a simple program, A TOOL, that can just compile actual code in an incremental and parallel fashion using standard markup format? Why does the build tool have to do dependency management AND compile C code? I think the reason we get these crappy DSLs that are hard to work with for compiling code is because we try to mash together actual compilation and dependency management which needs all these edge cases and special scripts to make work. Split the problem, the entire build process, into smaller modular tools, instead of trying to do every goddamn thing and the kitchen sink in one tool. Each individual tool can handle one part of the overall build process much better than the whole. You then string all the tools together using a top level script.

    I work in embedded and the unfortunate truth is that there are no dedicated build tools made for embedded. Embedded build tools are an afterthought, so people just try to make build systems for embedded by hacking cmake to make it work with those projects. We are stuck with cmake, when its complete overkill for 99% of the projects. There are no dependencies. LIbraries and headers are manually copied from project to project. I literally just need the build system to COMPILE code. You know the most important part of any C build system? COMPILING code. Not dependency management. Not checking if my compiler works on my dev platform. NOT producing goddamn code for windows, mac, or linux machines. CROSS COMPILATION ONLY. I just want a tool that doesn’t make ANY assumptions about compiler flags or compilers or how my project directory structure is laid out. I can do the leg work of inputting all the exact parameters and build configurations into the tool. I just need the tool to incrementally compile my code in a parallel fashion without HACKS and makefiles and being a “build system generator”. Believe it or not, incremental compilation is not a hard problem to solve. cmake would have you think it is, and that is part of the fracking reason its a build system generator and can’t do the fracking work of actually compiling code itself.

    TLDR: Working on a “compilation driver” tool that just compiles code in an incremental and parallel fashion without “rules” and without hacks. Just pass the tool a bunch of configuration files that add source files to the build and it will incrementally compile the project. Source file dependencies on header files? EVERY modern compiler provides a header dependencies option. Funny how cmake still tries to do its own parsing of header files. Here is an idea. How about you just tell my tool what the dependency command line option is and the format of the output file it generates. Then my tool can parse that file and store the information in a database. Novel idea that has been around for what? 15 years? FUNNY how cmake still drags around its legacy header dependency management, because its a piece of GARBAGE.

  11. Pingback: CMake projects in Visual Studio — Modeling and recognition of 2D/3D images

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s