Make is great as a dependency resolution engine. For everything else, it is absolutely horrible. What I typically do is use make only for what it is good: as a dependency resolution back-end. All the build logic for my projects is written in Python, in an executable file stored in the project root directory and called "make" (I have "." in my PATH). The Python script, when it runs, generates on the fly a clean, lean,…
This way we can e.g. have repos using completely different technology stacks but the interface to them is the same - whether it's our database, a node.js webservice, a python data analytics tool etc. and the definition of these lifecycle commands in common.mk are totally trivial, they're just .phony. one-liner rules.