Question

I have a project with sources in the src/ directory and its subdirectories (e.g. src/foo/ and src/bar/), and the objects in the obj directory and the matching subdirectories (e.g. obj/foo/ and obj/bar/).

I use the following (smimplified) Makefile:

SOURCES=$(shell find src/ -type f -name '*.c')
OBJECTS=$(patsubst src/%.c,obj/%.o,$(SOURCES))

all: $(OBJECTS)

obj/%.o: src/%.c
    gcc -c $< -o $@

The problem

The problem is that if obj/ or one of its subdirectories doesn't exist, I get the following error:

Fatal error: can't create obj/foo/f1.o: No such file or directory

How can I tell make that %.o files depend on the creation of their containing directory?

What I tried

One solution when there are no subdirectories is to use "order only prerequisites":

$(OBJECTS): | obj

obj:
    mkdir $@

But that fixes the problem only with obj/, but not obj/foo and obj/bar/. I thought about using $(@D), but I don't know how to get all this together.

I have also used hidden marker files in each directory, but that's just a hack, and I have also put a mkdir -p just before the GCC command but that also feels hacky. I'd rather avoid using recursive makefiles, if that were a potential solution.

Minimal example

To create a minimal project similar to mine you can run:

mkdir /tmp/makefile-test
cd /tmp/makefile-test
mkdir src/ src/foo/ src/bar/
echo "int main() { return 0; }" > src/main.c
touch src/foo/f1.c src/bar/b1.c src/bar/b2.c
Was it helpful?

Solution

I don't know why you consider adding mkdir -p before each compiler operation to be "hacky"; that's probably what I'd do. However, you can also do it like this if you don't mind all the directories created all the time:

First, you should use := for assigning shell variables, not =. The former is far more efficient. Second, once you have a list of filenames it's easy to compute the list of directories. Try this:

SOURCES := $(shell find src/ -type f -name '*.c')
OBJECTS := $(patsubst src/%.c,obj/%.o,$(SOURCES))

# Compute the obj directories
OBJDIRS := $(sort $(dir $(OBJECTS))

# Create all the obj directories
__dummy := $(shell mkdir -p $(OBJDIRS))

If you really want to have the directory created only when the object is about to be, then you'll have to use second expansion (not tested):

SOURCES := $(shell find src/ -type f -name '*.c')
OBJECTS := $(patsubst src/%.c,obj/%.o,$(SOURCES))

# Compute the obj directories
OBJDIRS := $(sort $(dir $(OBJECTS))

.SECONDEXPANSION:

obj/%.o : src/%.c | $$(@D)
        $(CC) -c $< -o $@

$(OBJDIRS):
        mkdir -p $@

OTHER TIPS

I'd do it this way:

SOURCES=$(shell find src -type f -name '*.c')  # corrected small error

...

obj/%.o: src/%.c
    if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi
    gcc -c $< -o $@
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top