Reputation: 3244
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 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?
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.
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
Upvotes: 2
Views: 924
Reputation: 99144
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 $@
Upvotes: 1
Reputation: 100926
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 $@
Upvotes: 1