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 buildfrom the command line. - Make sees that
buildhas a dependency on$(OUTPUTS). OUTPUTSexpands toone.out two.out(since it’s all the files inINPUTSwith their extension replaced).- Make runs the dependent tasks
make one.outandmake two.out, which both correspond to the%.out: %.intask. You can think of this as running that task multiple times with different arguments. - The
%.out: %.intask buildsone.outandtwo.out. - Now that the dependencies have been satisfied, make runs the
buildtask.
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.