GNU Makefile rule generating a few targets from a single source file

The trick is to use a pattern rule with multiple targets. In that case make will assume that both targets are created by a single invocation of the command.

all: file-a.out file-b.out
file-a%out file-b%out: input.in
    foo-bin input.in file-a$*out file-b$*out

This difference in interpretation between pattern rules and normal rules doesn't exactly make sense, but it's useful for cases like this, and it is documented in the manual.

This trick can be used for any number of output files as long as their names have some common substring for the % to match. (In this case the common substring is ".")


Make doesn't have any intuitive way to do this, but there are two decent workarounds.

First, if the targets involved have a common stem, you can use a prefix rule (with GNU make). That is, if you wanted to fix the following rule:

object.out1 object.out2: object.input
    foo-bin object.input object.out1 object.out2

You could write it this way:

%.out1 %.out2: %.input
    foo-bin $*.input $*.out1 $*.out2

(Using the pattern-rule variable $*, which stands for the matched part of the pattern)

If you want to be portable to non-GNU Make implementations or if your files can't be named to match a pattern rule, there is another way:

file-a.out file-b.out: input.in.intermediate ;

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

This tells make that input.in.intermediate won't exist before make is run, so its absence (or its timestamp) won't cause foo-bin to be run spuriously. And whether either file-a.out or file-b.out or both are out-of-date (relative to input.in), foo-bin will be only run once. You can use .SECONDARY instead of .INTERMEDIATE, which will instruct make NOT to delete a hypothetical file name input.in.intermediate. This method is also safe for parallel make builds.

The semicolon on the first line is important. It creates an empty recipe for that rule, so that Make knows we will really be updating file-a.out and file-b.out (thanks @siulkiulki and others who pointed this out)