5

I have a project where I need to process many files in the same way. My GNUMakefile looks like this:

$(SOURCES) = file1.a file2.a file3.a ... fileN.a
$(TARGETS) = $(SOURCES:.a=.b)

%.b: %.a
    build-tool $?

all: $(TARGETS)

This makefile executes build-tool for each target, which is slow. So I want it to execute build-tool only once with list of all updated prerequisites.
I tried to add the following rule:

$(TARGETS): $(SOURCES)

And it helped on first build like that:

$ make all
build-tool file1.a file2.a ... fileN.a

But on subsequent builds build-tool was executed as many times as many targets I have:

$ touch file1.a && make all
build-tool file1.a
build-tool file1.a
...
build-tool file1.a

In the other hand, if I change recipe to build-tool $@ it will execute build-tool once with all prerequisites in all cases, which is undesirable too.
Is there some method to execute the recipe only once for all targets, i.e. with all prerequisites on first build and only modified on subsequent builds?

2 Answers2

5

You can't use a pattern rule for this. That's why you're seeing everything build multiple times. You have to use a "sentinel" file that make can use to determine which files have changed since the last time you built.

For example, something like this will work:

$(SOURCES) = file1.a file2.a file3.a ... fileN.a
$(TARGETS) = $(SOURCES:.a=.b)

.PHONY: all
all: .ran-build-tool

.ran-build-tool: $(SOURCES)
        build-tool $?
        @touch $@

ETA: If you need another level you can do something like:

$(SOURCES) = file1.a file2.a file3.a ... fileN.a
$(TARGETS) = $(SOURCES:.a=.b)

.PHONY: all
all: .ran-build-tool2

.ran-build-tool2: $(TARGETS) | .ran-build-tool
        build-tool2 $?
        @touch $@

.ran-build-tool: $(SOURCES)
        build-tool $?
        @touch $@
MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • It solves my problem, but I can't figure out how to use it in more complicated case. For example, if I need to make `.c` files from `.b` ones with `build-tools2` in the same way as described above. – insanedeveloper Jan 16 '14 at 11:25
  • 1
    Sorry, but until you accurately describe the "more complicated case" it's hard to answer your question. On SO, as with anywhere, you get the answer to the question you asked, not the answer to the question you wanted to ask. Without full details of exactly how `build-tool2` works, I added a possible solution to my answer. – MadScientist Jan 16 '14 at 12:47
  • 1
    This would not work if somebody touch any of the `$(TARGETS)`. Make will not detect if these files are modified or deleted. – nowox Nov 03 '16 at 07:43
  • 1
    I''m not sure what you mean. make never detects if _targets_ are touched/modified. As long as their timestamp is newer that their prerequisites make will be satisfied. Make doesn't keep a database of what the previous timestamp was so it can't know whether it's been changed. However in my second example, make _will_ detect if `$(TARGETS)` have changed and will run the `build-tool2` command, since one or more will be newer than the `.ran-build-tool2` target. – MadScientist Nov 03 '16 at 12:20
  • NB If you have a `clean` target, you also need to remove `.ran-build-tool`. – Colin 't Hart Mar 06 '21 at 12:48
0

According to an answer to "multiple targets from one recipe and parallel execution" you may write something like:

file1%b file2%b file3%b: file1%a file2%a file3%a
        build-tool $?
abukaj
  • 2,582
  • 1
  • 22
  • 45