makefilegnu-make

Makefile: implicit rule with sub dirs


How to write an implicit rule to cover all sources in a sub directory with more sub directories and build objects in the current directory? Here is an example:

rootdir
├─ src
│  ├─ main.c
│  └─ sub
│     ├─ file.c
│     └─ file.h
└─ Makefile

Let's say, we have 2 source files src/main.c:

#include <stdio.h>
#include "sub/file.h"

int main(int argc, char *argv[]) {
    printf("2 + 3 = %d\n", myadd(2, 3));
    return 0;
}

and src/sub/file.c:

int myadd(int a, int b) {
    return a + b;
}

a Header src/sub/file.h:

int myadd(int a, int b);

And a Makefile:

all: tool

# GENERATED PART START
main.o: src/main.c src/sub/file.h
sub/file.o: src/sub/file.c src/sub/file.h
# GENERATED PART END

%.o: src/%.c
    mkdir -p $(dir $@)
    gcc -c -o $@ $<

tool: main.o sub/file.o
    gcc -I src -o $@ $^

.PHONY: all

My expectation is, that the rules create main.o and sub/file.o and build tool:

rootdir
├─ src
│  ├─ main.c
│  └─ sub
│     ├─ file.c
│     └─ file.h
├─ sub
│  └─ file.o
├─ main.o
├─ Makefile
└─ tool

But make creates only main.o and complains, that the linker cannot find sub/file.o:

mkdir -p . 
gcc -c -o main.o src/main.c
gcc -I src -o tool main.o sub/file.o
/usr/bin/ld: cannot find sub/file.o: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:18: tool] Error 1

What's wrong here?

Note: If I put the objects in a sub directory obj, and write the implicit rule like obj/%.o: src/%.c, it works fine. But that is not what I need. I have to build the objects in my rootdir.


Solution

  • You are falling afoul of the special handling of pattern rule matching describe in the second paragraph of How Patterns Match. Most of the time this is what you want, but unfortunately it causes issues in rare situations like this.

    The simplest solution is to use a static pattern rule, rather than a pattern rule; static pattern rules don't use this special rule:

    OBJS = main.o sub/file.o
    
    $(OBJS) : %.o: src/%.c
            mkdir -p $(@D)
            gcc -c -o $@ $<
    
    tool: $(OBJS)
            gcc -I src -o $@ $^
    

    BTW, it's a good idea to use make variables like $(CC), etc. rather than hardcoding gcc. It will make your future life much simpler.