Abstractions are perfectly possible, you just do it in your own script (Python or whatever) and this way you don't have to learn a new language. Ninja is a better alternative to Make, and it is even less expressive (which is one of the reasons why it is better, IMO).
Or in other words, we build abstractions in Make by composing it with other tools, rather than creating a huge monolithic Makefile syntax which can do anything you want.
Here are the recipes, most recent version: https://github.com/marssaxman/ozette/blob/master/srctree.mk
And this is an example of a makefile using those recipes: https://github.com/marssaxman/ozette/blob/master/Makefile
This ability to write and compose is a rare feature today. Json is pretty opposite to it and even Yaml is rather bad at it as to compose pieces one have to parse them first.
It's like saying that "Why use assembly at all? GCC could generate anything." Well, yes. But you're on an x86 machine and there's an assembler right there for GCC to use, and GCC has been using that assembler for decades.
Manipulating files is trivial in any programming language expect maybe in something like C,C++,Java,etc.
Python/js/go make it really easy to deal with complex build systems or even just 1 liners.
So I have no idea why make has any advantage over those - only use-case is if you are writing C where its non-trival to do file-handling.
I moved to npm scripts once I asked the question "how do i make a multi-process build script in make ?"
and all the neck-beards in uni had no answer - so I just write small scripts which grow and shrink based on my needs.
No idea how you missed the -j option.
This is why I think everyone should learn programming first and linux second - since many linux tools are terrible.
As expressed in the SICP book - the programming language should allow you to build abstractions. Make doesn't at all allow that.