Making directories in GNU Make

[article]
Summary:

This article looks at a variety of ways to achieve directory creation in GNU Make and points out a common trap for the unwary.

This article looks at a variety of ways to achieve directory creation in GNU Make and points out a common trap for the unwary.

A simple example

The following simple Makefile builds an object file /out/foo.o from foo.c using the GNU Make built in variable $(COMPILE.C) to make a .o file from a .c by running the compiler.

foo.c is in the same directory as the Makefile, but foo.o is placed in /out/.

.PHONY: all
all: /out/foo.o

/out/foo.o: foo.c
    @$(COMPILE.C) -o $@ $<

This example works fine as lone as /out/ exists, if it does not you\'ll get an error from the compiler along the lines of

Assembler messages:
FATAL: can\'t create /out/foo.o: No such file or directory
make: *** [/out/foo.o] Error 1

Obviously what you\'d like to do is have the Makefile create /out/ automatically if it is missing.   

What not to do

Since GNU Make excels at making things that don\'t exist when necessary it seems obvious to make /out/ a prerequisite of /out/foo.o and have a rule to make the directory.  That way if we need to build /out/foo.o the directory will get created.

Here\'s the reworked Makefile with the directory as a prerequisite and a rule to build the directory using mkdir.   To make things simple I\'ve stored the name of the output directory in variable OUT so that I don\'t have to keep repeating myself, and I\'ve specified the -p option on the mkdir command which will be all the necessary parent directories.  In this case the path is simple, it\'s just /out/ but -p means that mkdir could make a long chain of directories in one go.

OUT = /out

.PHONY: all
all: $(OUT)/foo.o

$(OUT)/foo.o: foo.c $(OUT)/
    @$(COMPILE.C) -o $@ $<

$(OUT)/:
    mkdir -p $@

This works well for this simple example, but there\'s a major problem.  Since the timestamp on a directory is typically updated when any of the files inside the directory are updated this Makefile does too much work.

For example, just touching a random file inside /out/ forces a rebuild of /out/foo.o.  In a complex example this could mean that many object files are rebuilt for no good reason, just because other files were rebuilt in the same directory.

Solution 1: build the directory when the Makefile is parsed

A simple solution to the problem above is to just create the directory when the Makefile is parsed.   A quick call to $(shell) can achieve that.  Before any targets are created or commands run the Makefile is read and parsed.  If you put $(shell mkdir -p $(OUT)) somewhere in the Makefile then GNU Make will run the mkdir every time the Makefile is loaded.

OUT = /out

.PHONY: all
all: $(OUT)/foo.o

$(OUT)/foo.o: foo.c
    @$(COMPILE.C) -o $@ $<

$(shell   mkdir -p $(OUT))

The disadvantage of the simplicty is that if there are many directories to be created this could be slow.  And GNU Make is doing unnecessary work since it will attempt to build the directories every time you type make.

Solution 2: build the directory when all is built

A related solution is to only build the directory when all is being built.  This means that the directories won\'t get created every time the Makefile is parsed (which could avoid unnecessary work when you type make clean or make depend).

OUT = /out

.PHONY: all
all: make_directories $(OUT)/foo.o

$(OUT)/foo.o: foo.c
    @$(COMPILE.C) -o $@ $<

.PHONY: make_directories
make_directories: $(OUT)/

$(OUT)/:
    mkdir -p $@

TODO use a %/ pattern rule

Another disadvantage of this technique is that you must specify make_directories as a prerequisite of any target that the user might specify after

About the author

AgileConnection is one of the growing communities of the TechWell network.

Featuring fresh, insightful stories, TechWell.com is the place to go for what is happening in software development and delivery.  Join the conversation now!