It is so trivial, it takes you 10 minutes to learn.
Less even, here is an attempt:
foo.o: foo.c
clang -c -o foo.o foo.c
This builds a file called foo.o, if foo.c is newer than foo.o. Imagine that there is an identical bar.o as well, that builds it from bar.c.
fooexec: foo.o bar.o
clang -o fooexec foo.o bar.o
This links together foo.o and bar.o into a file called fooexec, if at least one of foo.o or bar.o is newer (which in turn means that any of
its respective dependencies changed; e.g. change bar.c, and bar.o and then fooexec will rebuild).
Now type this into the shell:
make fooexec
Congratulations! You have a fully built fooexec. Now change bar.c, type it again, and like magic, you have a rebuilt bar.o and fooexec. foo.o will stay put, it’s up to date!
But of course all the .c -> .o rules look the same, so instead of writing a new one for every file, you'd use simple wildcards, and now the whole, entire Makefile is just:
%.o: %.c
clang -c -o $@ $<
fooexec: foo.o bar.o
clang -o fooexec foo.o bar.o
Do $@ and $< look arcane at first glance? Yes. Is what they do simple, though? Also yes. (If your complaint is that you don't know what those "clang" lines do, then you don't know how to use the C compiler, and will have the same problem when writing a python script.)
It pains me if that is not immediately obvious. And it pains me even more that someone would want to write a boilerplate heavy, complex python program, that rebuilds everything all the time, instead of just learning this very simple concept.