Nicolas Wavrant

Coding for humanity

Write a Makefile rule applying to all files in a directory

Today I learned a cool Makefile feature. For one of my websites, I had a makefile converting all non-optimized images from a directory. It looked like this:

all: photo1.jpg photo2.jpg photo3.jpg photo4.jpg
%.jpg: img/%.jpg
convert -strip -interlace Plane -quality 75% $< $@

This Makefile defines a rule for all .jpg files in the current directory. They are built using the corresponding file (= with the same name) from the "img" directory. To convert all images at once, I added a rule "all" that contains a list of targets.

The issue with this Makefile is that if I want to add a new image, I need to not forget to add it to the Makefile.

Today,  I found out that it's possible to define variables and run functions in a Makefile, to automatically create the list of targets. Here is the new version of the Makefile:

SOURCE_DIR := ./img
DEST_DIR := .
# Define a list of source files using wildcard
SOURCE_FILES := $(wildcard $(SOURCE_DIR)/*)
# Generate a list of corresponding destination files
DEST_FILES := $(patsubst $(SOURCE_DIR)/%, $(DEST_DIR)/%, $(SOURCE_FILES))
# Define a pattern rule to convert files from SOURCE_DIR to DEST_DIR
$(DEST_DIR)/%: $(SOURCE_DIR)/%
convert -strip -interlace Plane -quality 75% $< $@
all: $(DEST_FILES)
# Debug function
print_variables:
@echo "SOURCE_FILES: $(SOURCE_FILES)"
@echo "DEST_FILES: $(DEST_FILES)"
.PHONY: all print_variables

In short, it works like this:

  1. It builds a list of source files in SOURCE_FILES
  2. It creates a list of DEST_FILES, modifying the path of each source file. This list of destination files become the list of targets