Build all files with a given extension
The most basic use of Make (other a simple task runner) is reading some files, compiling them somehow and outputting other files.
Here’s a minimal example that takes an existing file with extension .in
(some/dir/anything.in
) and writes “Created from filename.in” to a sibling file with the extension .out
:
%.out: %.in
echo "Created from $<" > $@
$<
and $@
are two special sigils that are replaced with the actual filenames of the target and the first dependency, respectively. So if make were invoked with make some/dir/anything.out
, the expanded command would read echo "Created from /some/dir/anything.in" > /some/dir/anything.out
.
Note that the target needs to correspond to a specific input file that exists on the filesystem; if it doesn’t, make will say something like make: *** No rule to make target `nonexistentfile.out'. Stop.
In order to build all files with a given extension, we need to use wildcards. Makefile tutorial has a short summary of the different types, and it’s not entirely clear to me what the difference is, but here’s my best understanding of how to use them:
INPUTS := $(wildcard *.in)
OUTPUTS := $(patsubst %.in,%.out,$(INPUTS))
%.out: %.in
echo "Created from $<" > $@
build: $(OUTPUTS)
echo "Built!"
This creates two variables, INPUTS
and OUTPUTS
. For INPUTS
, $(wildcard *.in)
will be expanded to all files with the extension .in
. The list can be filtered further: $(wildcard src/**/*.in)
will only show files in descendent directories of src
. For OUTPUTS
, $(patsubst %.in,%.out,$(INPUTS))
changes the extension of every .in
file name in INPUTS
to .out
.
The task build: $(OUTPUTS)
will build all the input files. Let’s say there are two files adjacent to the makefile, one.in
and two.in
. It’s easiest to think of this in a few steps:
- Run
make build
from the command line. - Make sees that
build
has a dependency on$(OUTPUTS)
. OUTPUTS
expands toone.out two.out
(since it’s all the files inINPUTS
with their extension replaced).- Make runs the dependent tasks
make one.out
andmake two.out
, which both correspond to the%.out: %.in
task. You can think of this as running that task multiple times with different arguments. - The
%.out: %.in
task buildsone.out
andtwo.out
. - Now that the dependencies have been satisfied, make runs the
build
task.
The benefit to using make is that if we run through the steps again, it will only re-run the %.out: %.in
task for .out
files that are older than the corresponding .in
files.