There are a number of python libraries I can’t install on my laptop easily because the native code runs out of physical (4GB!) memory spawning compiler jobs and everything starts thrashing, it’s just dumb to go forking without asking the user first. People who really want it can give make the -j flag.
Manually specifying resources doesn't work well either because it uses 2x+ the resources you allocate it.
Especially painful when one of your coworkers has a beefier machine than you and changes the number of parallel jobs offhand one day, causing your computer to freeze until the OOM killer gets the right build process.
The only way to properly solve missing dependencies is to hide targets unless they are explicitly depended on. The only systems I know that do that are Nix and Bazel (and its clones).
If make knows about all the inputs and outputs of every rule, it can make sure dependencies are built before they are used. The problem occurs when the makefile doesn't tell make about every dependency.
Also the rule scripts themselves can have race conditions. A trivial example is if they end up using the same temp filename when doing work.
For example if you have two targets, A and B, and B depends on A's output but you forgot to say so, then `make` can build B before A even if you don't use the `-j` option.
So if you have:
all: A B
Then if both need a rebuild A is always first, except when -j breaks that.
But I can't say I have played close attention to every time I have typed make.