There are times in a Makefile where you need a common dependency built first. The most obvious one is the output directories. There are a bunch of options for this, including sticking a
mkdir -p $(BUILDDIR) in every target or adding it as a dependency. This can result in a lot of noise and duplicate command invocations (and becomes an issue if the command isn’t idempotent or slow). The other problem is you don’t want to keep rebuilding targets because of this dependency (e.g. the build directory timestamp changes).
I’ve always wanted an ability to only trigger a common target once during the build if it’s needed. I found out it’s called an “order-only” prerequisite.
BUILDDIR=$(CURDIR)/build $(TARGETS): $(SOURCES) | $(BUILDDIR) $(BUILDDIR): mkdir $(BUILDDIR)
The important part is the
|. This adds
$(BUILDDIR) as a dependency but doesn’t affect re-building of the
I’ve used this in two different situations in the past: making the output directories and setting up my Python virtualenv (you can also kind of coax multiple
docker up invocations into working with this).
Here are three examples of Makefiles doing the same thing, making a build directory and “building” files into it. The last uses order only prequisites.
mkdir -p in Every Target
This is probably the most direct approach, create your dependencies in the target each time. Note the use of
-p, that prevents the second attempted mkdir from failing. This approach is noisy and won’t work well when you need something more complicated (e.g. install dependencies).
BUILDDIR=mkdir-build TARGETS=$(BUILDDIR)/1.out $(BUILDDIR)/2.out .phony: all all: $(TARGETS) $(BUILDDIR)/1.out: 1.in mkdir -p $(BUILDDIR) touch $@ $(BUILDDIR)/2.out: 2.in mkdir -p $(BUILDDIR) touch $@
❯ make mkdir -p mkdir-build touch mkdir-build/1.out mkdir -p mkdir-build touch mkdir-build/2.out ❯ touch mkdir-build ❯ make make: Nothing to be done for 'all'. ❯ touch 1.in ❯ make -f mkdir-makefile.mk mkdir -p mkdir-build touch mkdir-build/1.out
Here you can see the
mkdir -p keeps getting re-run.
This is pretty close to an optimal approach, now Make is responsible for building it as a target. Now you have the problem where touching the dependency will re-trigger builds on all your targets. Not necessarily a big problem, but probably annoying.
BUILDDIR=dependencies-build TARGETS=$(BUILDDIR)/1.out $(BUILDDIR)/2.out .phony: all all: $(TARGETS) $(BUILDDIR)/1.out: 1.in $(BUILDDIR) touch $@ $(BUILDDIR)/2.out: 2.in $(BUILDDIR) touch $@ $(BUILDDIR): mkdir $(BUILDDIR)
❯ make mkdir dependencies-build touch dependencies-build/1.out touch dependencies-build/2.out ❯ touch dependencies-build ❯ make touch dependencies-build/1.out touch dependencies-build/2.out ❯ touch 1.in ❯ make touch dependencies-build/1.out
Here you can see touching the build directory forces all the targets to be rebuilt.
mkdir is only run once though.
With Order Only Prequisites
This is probably the most optimal solution, Make handles building your dependency but won’t retrigger your target builds if it changes. Effectively you have two sets of targets, and one just asks Make to evalauate the other first. Hence “order only”.
BUILDDIR=order-only-build TARGETS=$(BUILDDIR)/1.out $(BUILDDIR)/2.out .phony: all all: $(TARGETS) $(BUILDDIR)/1.out: 1.in | $(BUILDDIR) touch $@ $(BUILDDIR)/2.out: 2.in | $(BUILDDIR) touch $@ $(BUILDDIR): mkdir $(BUILDDIR)
❯ make mkdir order-only-build touch order-only-build/1.out touch order-only-build/2.out ❯ touch order-only-build ❯ make make: Nothing to be done for 'all'. ❯ touch 1.in ❯ make -f order-only-makefile.mk touch order-only-build/1.out
Here you can see
mkdir is only run once and touching the build directory doesn’t force a rebuild of the targets.
Is this a game changer? Probably not, but there are definitely situations where it solves real problems. It’s also a bit neater :)