The project you landed on has been using some souped-up supermake with a ton of features (which were used with wild abandon) and some subtly different semantics for (say) macro expansion and variable assignment (and these are really difficult to determine the dependency scope of). The build system has grown to a recursively gnarly Turing-complete mess.
So when you go to update the tooling, you find out that this supermake is (a) a commercial project, (b) the company has gone out of business, or adopted a predatory per-seat licensing scheme, or no longer supports your platform, or has changed the product so significantly that you might as well port everything to a different version of make anyway.
Never happened to me.
Common make fail #2
I was regularly dealing with bugs in multi-million-line makefiles. Make, you see, has no debugger; you can't step through things to find out what's going on. To debug an issue you insert print (echo) statements and look at output, and/or gronk through many, many lines of make's internal structures. Why didn't file A compile, and why file B compile twice? It could take a long time to figure out.
Common make fail #3
"Builds are too slow, we need to speed them up."
So you go parallel. Hey, this is easy. Except for those libraries there (they need to be done first). And those headers are generated, so those need to come first, but first first. And everything depends on these libraries getting built, so need to wait for the linker here, here and here. Pretty soon you have a dependency hairball that nobody understands and that people are afraid to change because things always break. Eventually you get to a point where "yeah, building twice usually seems to fix that issue..." and that's when you know the project has grown too damned big for its shell again.
Common make fail #4
You die a little each time that you get screwed by tab-versus-space. And cross-platform . . . umm, what year is it? Then someone smugly mentions some XML-based tool to you, and you snap because that tool is worse in every single measurable dimension and a few more besides but the developer in question doesn't question things because the tool uses Holy XML and Blessed Java (well, some version of Java, anyway, you can probably still download it from somewhere) and who cares if it's just the same set problems with a different color paint slapped on top and the usual Reddit community dedicated to the elimination of all heretical views. Support? Who needs that, just read the source.
Seriously, I haven't seen any build tools in the past 30 years that have done a great job and that were a joy to use. CMake, Ant, Gradle, make-of-the-month-club, whizzy vendor-locked tools, bespoke in-house constructions, they have all been terrible. I see little hope, entropy has won this one.
Not starting by reading the manual, and then writing an idiomatic make file.
Once you understand how to use auto generated .d files, you’ll never have a multimillion line make file.
Once you understand the built in lex and yacc rules, you can generalize them to whatever code generator you are using.
The remaining thing (not spelled out in the manual) is to avoid recursive sub make, and to use the include directive instead. “Recursive submake considered harmful” is a helpful paper, but it should have had the subtitle “a practical alternative”, because people cite the title without reading the whole paper.
I've personally only used make for a few projects, and might not be "in the know" about how to best use make, but isn't a multi million (!?) line makefile a sign you should probably break it into peices, or use a different solution?
Multi-million just seems insane to me
Personal feeling is you should have a way of regenerating makefiles.
I guess my point is that make doesn't scale, and unavoidably gets messy over time as each project solves the same problems over and over in different, buggy ways.
make solves problems you get early in a project ("let's get this handful of files automatically built") and doesn't even try to address issues of modern software development, like build parallelism and dependency management. Its facilities for debugging problems are laughable.
Go look up plan9's mk. It's a remake of the program written by the designers a few decades later with everything they learned from it. Not only are the variables idiomatic, and the spaces problem fixed, but you can ask for output at each stage and (IIRC) view the graph that it builds.
Of course you quickly get to million-line Makefiles in bigger projects. In C++, if you depend on stuff like Boost, a single file can easily depend on thousands of header files, and every single one must be tracked by Make.
But of course, nobody in his right mind would write these dependencies by hand. At my dayjob, the core Makefile is 700 lines. These 700 lines do cross-compilation with gcc/clang for various platforms, unit testing, valgrind, cppcheck, clang-tidy, coverage, and several other things. It is completely non-recursive, runs in parallel without problems, tracks all dependencies, and also works on Windows.
Of course this stuff is complicated. Make is very low-level compared to a build-generator like CMake. But if you want a bespoke build system for your project which lets you control every last detail of your build, it is still a good choice.
> grown to a recursively gnarly Turing-complete mess
> gronk through many, many lines of make
Thank you for my new word of the day. It describes exactly how I feel sometimes, dealing with technical debt accumulated over years, Frankenstein's systems where parts are always churning - often for no good reason at all, new but worse in most aspects - it can be exhausting gronking through the layers of complexity.
Then Microsoft got rid of it because InstallShield gave them money. Now you need the super-extra-professional edition of Visual Studio to have anything but the most basic installer, and you need to hire a guy to write InstallShield scripts while he's taking a break from contemplating just how tough it would be to get by on pogey alone, and God forbid you need to change anything because the guy is on a different floor because he's not a real developer, etc. etc.
Have a look at redo[1].
Also, #3 is what make excels in. I'm still to see another tool that is more fit to that kind of thing.
At the end of the day, I don't like make. It's for reason #5: there's no project introspection, you can't group tasks into high level values, update your graph on the fly, or add anything to your target except at the source level. I see this one didn't make into your list. This is one of the reasons those multi-million makefiles exist.
I'd rather deal with a small shell script, or a larger Python one if it can't be small. Yet, autotools are great for system programming.
The most common mistake, by far, is trying to use make recursively (where make calls other makes down a directory hierarchy). That's typically where "builds are too slow" come from, because you end up with a hairball of complex workarounds because no one make invocation actually has the correct dependency data.
I just create a single makefile and treat it like any other program: Add carefully, ensure that the text is clear, etc.
I think that we're going to see more languages with built-in build systems from early-on in the language's development, but that is a doubled-edged sword (works great for your version of WHIZBOL, doesn't work great if you need to interop with other things).
When you (the general "you", please) use a tool wrong, you can look kinda a fool for blaming the tool.