У меня имеется список исходных файлов и список файлов в которые они должны быть скомпилированы.
SRCS = ./a/prog1.c ./a/b/prog2.c ./a/b/c/prog3.c
OUTS = ./outs/prog1.out ./outs/prog2.out ./outs/prog3.out
Это не очень удачная конфигурация, в том смысле, что немного изменив условия можно сильно упростить Makefile, но если очень хочется именно такого, то можно сделать так:
.PHONY: all
SRCS = ./a/prog1.c ./a/b/prog2.c ./a/b/c/prog3.c
OUTS = ./outs/prog1.out ./outs/prog2.out ./outs/prog3.out
all: $(OUTS)
define make_rule =
$(firstword $(1)): $(firstword $(2))
@echo compile $$< to $$@
gcc $$< -o $$@
$(if $(wordlist 2,$(words $(1)),$(1)),
$(eval $(call make_rule,
$(wordlist 2,$(words $(1)),$(1)),
$(wordlist 2,$(words $(2)),$(2)))))
endef
$(eval $(call make_rule,$(OUTS),$(SRCS)))
Здесь происходит вот что: макрос make_rule принимает на вход два списка, создаёт правило, что первое слово из первого списка зависит от первого слова из второго списка, а потом вызывает сам себя со списками из которых удалены первые слова, если эти списки не пустые.