I used to agree, but I eventually needed to add support in CMake for a particular IDE and got far too familiar with it. It's easy to debug and the syntax could be worse. Whoever came up with the list representation needs to be arrested though.
It might be stockholm syndrome, but I'd rank it worse than Meson and better than Makefiles. Autotools can go die in a fire.
Cargo is wonderful, but having something that user-friendly and well designed in C would feel incongruous with the rest of the ecosystem.
> Cargo is wonderful, but having something that user-friendly and well designed in C would feel incongruous with the rest of the ecosystem.
You really did say this.
Cargo does get some flak from C and C++ build systems. It is definitely not perfect, but which build system is?
The coolest thing about cargo is that it can be and is significantly improved over time. The whole dependency resolution was improved to resolve some long standing issues lately in a backward compatible way and that’s just astounding that they managed to ship this.
Do I like cargo? No. Do I like it better than CMake Meson Bazel Auto tools and make files? Hell yeah, by far, I’d rather get shot than go back to cmake or Auto tools .
Crazy, I'd definitely use CMake above autotools (does not even work natively on windows LOL), handwritten makefiles (not supporting filenames with spaces in 2021 LOL), "the IDE" (do you even do cross-platform?).
I'd use Bazel but afaik it mostly uses a rebuild-the-world approach.. I'm already getting flak from Linux distros maintainers because I use some vendored header-only libraries and not system ones so I don't see how that is even supposed to work.
Bazel does the opposite of rebuild-the-world. That's more of a CMake thing (rm -rf build && mkdir build && cd build && cmake ..). Bazel won't even run tests if the code the tests depend on have not changed. A lot of thought went into Bazel. It caches everything it possibly can.
> I'm already getting flak from Linux distros maintainers because I use some vendored header-only libraries and not system ones so I don't see how that is even supposed to work.
That's where you use autotools. It has been around long enough that every package manager knows how to deal with it. For most C/C++ programs, autotools is all you need. You should learn it because it's not going away.
"the IDE" (like VS project files) is definitely not cross-platform (although it can be), but not everything needs to be cross platform. For game devs building on DirectX, for example, it's completely pointless to support other platforms when the runtime depends on a single platform.
by "rebuild the world" I don't mean "do a clean rebuild" (which frankly isn't a problem in 2021 with CMake + Ninja, the last time I had to rebuild is when the compiler changed from clang-11 to clang-12) but "to build a bazel-based software, all its dependencies must be built with bazel too", e.g. it's harder if you want to link against system-provided Qt, ffmpeg, ...
> That's where you use autotools. It has been around long enough that every package manager knows how to deal with it.
and yet it still sucks on windows if you want to use cl.exe (or if you want Xcode / VS solutions, which is regularly my case).
I think I had the best experience with Meson/Ninja so far. I am also interested in using Nix for building. As for Cargo, I did not like how it recompiled all dependencies when I changed a warning flag on my project. I also found it unusable because it provided no way to check for the hash or signature of the dependencies that it downloads.
I don't think that I have ever been able to successfully compile a project that uses CMake. Its code is horrifying too, for example cmake-3.21.0-rc3/Modules/CheckFunctionExists.c contains
#ifdef CHECK_FUNCTION_EXISTS
# ifdef __cplusplus
extern "C"
# endif
char
CHECK_FUNCTION_EXISTS(void);
# ifdef __CLASSIC_C__
int main()
{
int ac;
char* av[];
# else
int main(int ac, char* av[])
{
# endif
CHECK_FUNCTION_EXISTS();
if (ac > 1000) {
return *av[0];
}
return 0;
}
#else /* CHECK_FUNCTION_EXISTS */
# error "CHECK_FUNCTION_EXISTS has to specify the function"
#endif /* CHECK_FUNCTION_EXISTS */
> I think I had the best experience with Meson/Ninja so far.
cmake lets you use ninja as the backend if that's your cup of tea. You can even set it to the default generator
, by setting the CMAKE_GENERATOR environment variable to ninja. (I have no meson experience, so can't compare it).
> I don't think that I have ever been able to successfully compile a project that uses CMake.
That's quite the statement. In practice, I've found
cmake -h. -Bbuild && cmake --build build
to work about 90% of the time. Far more luck than I've had with autotools.
> Its code is horrifying too, for example:
1) I'm sure I could find some horriffic code in meson too if I went digging. 2) The alternative to this is you having to write something equivalent in your own code, meaning that in my code I don't need to do stuff like [0] in my code to detect features; my build system handles it for me. 3) CMake supports more platforms and targets than I've ever seen in my life, and likely supports more compilers than are necessary. that's a blessing and a curse, but it means that if I write simple program to run on some crufty microcontroller with a bastardised gcc toolchain from the 90s, it's fairly likely that cmake supports it out of the box. Code like that is the price to pay for that level of support.
You missed the point. "__CLASSIC_C__" is not a thing (why they don't use __STDC__? I don't know, they don't seem to know either) and the syntax that they use inside that ifdef is.. not what people mean by classic C. It has been there for years and multiple people have pointed it out but they do not seem to care. The funny thing is that they do know how to use the pre-standard C argument syntax (as in https://gitlab.kitware.com/cmake/cmake/-/blob/master/Modules...), it's just that they do not want to fix it for that specific file for some reason.
As for
if (ac > 1000) {
return *av[0];
}
return 0;
I am not really sure what to say.
And then for CHECK_FUNCTION_EXISTS();, there are a few rare compilers that do not throw an error at compile-time if said function does not exist.
Also, I have been told that cmake-generated Makefiles invoke cmake itself, so you can't really generate portable Makefiles with it. In addition to that, I have been told that cmake takes ages to compile.
> meaning that in my code I don't need to do stuff like [0] in my code to detect features
> You missed the point. "__CLASSIC_C__" is not a thing (why they don't use __STDC__?
I dont know, I'm not going to defend it. Imnot going to do a line by line review of the file you picked, as I said I'm sure I can find awful code in bazel, meson, etc.
> Also, I have been told that cmake-generated Makefiles invoke cmake itself, so you can't really generate portable Makefiles with it.
Cmake generates a target for makefile that will re run cmake if your cmake file changes. If you're bukldong with cmake, you distribute the cmakelists txt, and treat the makefiles, ninja files etc as build intermediates
> In addition to that, I have been told that cmake takes ages to compile.
Do you compile your own make regularly? I've compiled cmake once or twice and it's not quick, but it's definitely doable (maybe 5 or so minutes?)
> I find that much better honestly.
The reason to use a build tool is to avoid hacks like that in user code. I would rather have cmake or meson or whatever my meta build tool is handle and test that logic, so I can focus on my library or application code.
> as I said I'm sure I can find awful code in bazel, meson, etc.
Oh, you have proof that person X murdered someone? I am sure I can find awful stuff that person Y and Z did!
[back in 2014] OpenSSL has heartbleed? Well, I am sure that I can find issues in libsodium if I looked.
> Do you compile your own make regularly?
No, my users however might need to once they have to deal with a cmake project.
> but it's definitely doable (maybe 5 or so minutes?)
I was told that it takes hours, though I might be misremembering.
> so I can focus on my library or application code.
This kind of thing does not really distract you from anything. Adding something like that takes as long as it does to add a cmake check. Then the person who is compiling has to do -DENABLE_FEATURE=1.
> Oh, you have proof that person X murdered someone? I am sure I can find awful stuff that person Y and Z did!
No - that's not what I'm saying at all. I'm saying that if you're holding X to a standard, you should hold Y and Z to the same standard.
> No, my users however might need to once they have to deal with a cmake project.
cmake is available from the package manager on basically every system imaginable, or as a binary (or source) download for a whole host of platforms. It's also widely used, so chances are a user is going to have it installed.
> I was told that it takes hours, though I might be misremembering.
If you're going to dogmatically claim that cmake is inferior, then you should at least verify the grounds of your claims are true.
I ran
in under 90 seconds. Might have even been faster if I used ninja. I actually don't konw how to compile make from source on windows. I had a look, and apparently I need to ftp the source from a gnu mirror?
> This kind of thing does not really distract you from anything. Adding something like that takes as long as it does to add a cmake check. Then the person who is compiling has to do -DENABLE_FEATURE=1.
This isn't unique to cmake but a meta build system does more than just let you add defines.
>And then for CHECK_FUNCTION_EXISTS();, there are a few rare compilers that do not throw an error at compile-time if said function does not exist.
check_function_exists() verifies that the symbol can be linked to rather than compile. That's why it gives it a bogus declaration of char CHECK_FUNCTION_EXISTS().
Funny enough I was trying to build a library yesterday that used check_function_exists() to detect the presence of some library functions. The project was set up to output a static library so check_function_exists() returned true for all the missing functions since it linked the test program without issue. https://gitlab.kitware.com/cmake/cmake/-/issues/18121
"cargo update" will update the packages along with Cargo.lock content. As for not updating them without a manual trigger, I consider it a feature, but I guess it's a matter of opinion.
If you have a project that you don't want to embed in another project, doesn't build on windows, doesn't have any spaces in filenames, and do'nt mind manually ordering dependencies wrt the quirks of your toolchain.
I don't see how make is at a disadvantage with relation to other tools with embedding. No build tool is easily embedded in a project that uses a different build tool.
And I've used make on Windows for years and years, so not sure why that is a problem?
My understanding is that recursive make is considered an antipattern (and I'm not aware of any other way to embed makefiles in other makefiles). Cmake's add_subdirectory is first class support for nesting other projects,and the FindX.cmake pattern means you can consume prebuilt libraries inside cmake, which I don't think make supports?
You are technically right about make on windows, but in practice makefiles on windows tend to be written for GCC/clang, and require manual translation to work with MSVC. There's also no IDE support for visual studio, which cmake gives you out of the box;
Handwritten makefiles can be the best option on the table in scenarios not involving a lot of boilerplate code or having to do any platform check at all.
Once you stray out of that niche, anyone is better suited if they just automate all the boilerplate generation and compiler checks.
Maybe it wasn't your intention, but your comment is the most scathing critique I've ever read of cmake.
Boilerplate code and "compiler checks" are strongly negative anti-patterns. Maybe the worst in programming. That cmake makes it easy to do these awful things just shows how evil it is!
That isn't always how it works in practice, though. It's useful for your build system (or meta-build system) to be able to check for, say, C++17 support, and cause the build to fail early if this is missing.
Similarly, you can use a tool like CMake to detect libraries. If a library is missing, you might want the build (or, rather the 'configure' stage) to instantly fail, or you might want to build with certain features removed to cope with the library being missing. CMake supports both, as does autotools.
I agree with everyone who says CMake's scripting language is atrocious and that it's often miserable to work with, [0][1] but that's because CMake specifically is terrible. The problem it's solving is a legitimate one.
An example: you can write a desktop application with two different platform-specific front-ends, and have CMake compile the appropriate one given the target platform. This is nicer than relying on the two platform-specific build systems directly.
>, you can use a tool like CMake to detect libraries. If a library is missing, you might want the build (or, rather the 'configure' stage) to instantly fail, or you might want to build with certain features removed to cope with the library being missing. CMake supports both, as does autotools.
The gp enriquto had a rigid stance on so-called "optional" libraries. If the library is not there, the build should fail and that's it.
Hey! I remember that interaction (and sorry for my crass language, I was having a hard time by then).
Your explanation was certainly satisfactory. Now I understand a bit the motivation of people who want to compile different programs depending on what happens to be installed at a particular moment in their computers. I still think that it is "an exceptionally bad idea which could only have originated in California" ;)
that does not work as soon as your users are on both windows and a unix-like platform and you are making a non-trivial app (for instance, an app with networking, audio, and video support).
A simple example: how do you share a GPU texture handle across multiple processes portably with a single code that works across windows / mac / linux's graphics APIs ?
Trying to do that is a massive waste of time. It's legitimately easier to maintain separate codebases for each of the different platforms. The code that doesn't need to change can be a library. Or you use some super high level abstraction like Unity, Qt, JVM, Electron, React Native. Portability != cross-platform.
How do you memory map a file, or spawn a subprocess simply and portably? Every way that I've seen it done that doesn't hand the logic off to the build system is an unmaintainable error prone mess.
I'm not sure you understood what I've said at all.
CMake eliminates boilerplate code and compiler checks. They do not exist, at all. With CMake you state that you have a C++11/14/17/20 project, it builds N static/shared libs and M executables, you set dependencies, and you're done.
They do exist in Makefile projects because Makefiles only define the DAG for the build, and don't perform any sanity check at all. So if you have to include dependencies or use specific compilers then you have to manually check each and every single thing yourself, because Makefiles do not handle that at all.
Think about it: why did the entire industry adopted makefile generators such as CMake instead of just using a standard tool like make, which just works and is ubiquitous?
And no, relying on tools and a layer of abstraction to eliminate all janitorial work is not evil or awful. Checking if a lib you depend on already exists in the system is not evil or superfluous. Checking if the compiler you're using supports a specific version of C++ is not evil or superfluous. Do you expect things to just work when you aren't even aware of which compiler you're going to use? Do you want to spend time looking at weird compiler error dumps just because your build machine happens to have a different version of, say, Boost installed?
The main problem of cmake is that some people seem totally oblivious to the problem domain, and what/how much work it takes to get stuff to work reliably given very basic usecases such as... Upgrading a version of a compiler, such as VS. Think about it: How exactly do you think simple, portable code is done? Do you expect code to compile on different platforms by magic?
> Checking if the compiler you're using supports a specific version of C++ is not evil or superfluous.
It is both evil and superfluous. It is evil because you should be writing portable code and do not depend on compiler specificities. It is superfluous because if you do not check, the compilation will still fail, which is precisely the expected behaviour.
> How exactly do you think simple, portable code is done?
By writing it carefully and testing it on different systems. You test with -Wall -Wextra -Werror -pedantic on all systems but you distribute the Makefile without these compiler flags.
> Do you expect code to compile on different platforms by magic?
No, of course. At first you will have a few linuxisms or macOS-isms, that you will gradually remove through a few rounds of multi-platform testing (which is free and easy to do nowadays).
> It is both evil and superfluous. It is evil because you should be writing portable code and do not depend on compiler specificities. It is superfluous because if you do not check, the compilation will still fail, which is precisely the expected behaviour.
But now I get 2 dozen error reports because there is a lot of users who run builds because they're on Linux and that's what $BLOGPOST said to do to have the last version of some software, but have no clue about software development.
> No, of course. At first you will have a few linuxisms or macOS-isms, that you will gradually remove through a few rounds of multi-platform testing (which is free and easy to do nowadays).
So how do you handle that you need to link against Ws2_32 on windows or "-lwebsocket.js -s PROXY_POSIX_SOCKETS=1 -s USE_PTHREADS=1 -s PROXY_TO_PTHREAD=1" on emscripten if you want to use sockets ?
Don't use sockets because they're not portable ?
I disagree, to the point I honestly doubt you are not trolling, specially taking into account how you decided to rank "the IDE" between autotools and handwritten makefiles, which makes absolutely no sense at all either way you look at it.
It's also dumbfounding how Bazel is ranked so high when it doesn't even support integrating system libraries as part of it's happy path.
The main reason why CMake, with all it's flaws, is the undisputed build system for c and C++ projects is the uncomfortable fact that all other alternatives are awful in comparison, even and specially in very basic happy path scenarios such as putting together a lib that any user can pick whether its static of shared and install it in the system folder or anywhere without even bothering with which compiler tool chain you're using. In CMake, anyone can get a complete project up and running with less than a dozen lines of code, and that project will assuredly work on multiple OSes as-is.
You'd be hard pressed to find another C++ build system that comes close to doing the same.
It might be stockholm syndrome, but I'd rank it worse than Meson and better than Makefiles. Autotools can go die in a fire.
Cargo is wonderful, but having something that user-friendly and well designed in C would feel incongruous with the rest of the ecosystem.