As I explained my motivation was primarily keeping my build description in my own datastructures (see the text database). IMO that's a very worthwhile goal. I then went on to see what it takes to NIH everything, and it turns out that detecting what files need to be rebuilt is hairy, especially considering transitive C includes (which requires a dependency cache). But there is no one solution to these problems, and I do think there is some value in controlling that logic.
And if you get the concepts right, it can be done. I've divided the code into loosely coupled building blocks. That hopefully makes it easier to understand. It should also be possible to recombine these blocks to make different build systems.
If the project allows it, though, your dependencies can be shrunk down until there's no issue. This is far more the case with target languages that already understand modules (e.g. Rust or Python versus C or JS), or "one big framework" projects where you are relying on linking in the one framework and having it do all the heavy lifting on the build side of things.
Actually customizing a build substantially beyond that, tends to be the case more often when you have something like a compiler within your own pipeline, like automatically transforming and clipping source art assets into optimized, UI-ready form. With those systems there is a lot to be said for a simple script that you can debug.