This is also not declarative vs imperative. It is declarative with a functionalty vs imperative without it. A shell script could also check which parts are missing or outdated and do incremental build. I’m not any good at shell scripting so here is a similar pseudocode:
import cc, ld, glob, make
// make defined as:
fn make(to, from, how) {
if (exists(to) && mtime(from) <= mtime(to) {
return
}
how(to, from)
}
make("foo.o", "foo.c", cc)
…
make("a.out", glob("*.o"), ld)
It’s basically the same as Makefile, except that out “make script” doesn’t know about goals anymore, only steps to take. Do we need that goal-knowledge in a declarative format is an open question, because you can wrap these make() calls in a function named aout() and that becomes your declarative goal. However declarative your config/data is, you still pass it to some evaluator like `make` at the end of the day.