0
votes

In my working directory, there is a makefile, an empty obj folder and a src folder that contains foo.c. I have a simple rule that takes an object file, substitutes everything until the first slash with src and replaces the ending .o with .c.

Example: obj/foo.o --> src/foo.c

With the help of MadScientist (credits to him) I obtained the pattern rule below (Please do not propose easier rules --> I am aware of their existance, but only this rule demonstrates my problem):

OBJFILES=obj/foo.o
all: $(OBJFILES)
    @echo "all executed"

Y = $(shell echo $1 | sed -e 's,^[^\/]*,src,' -e 's,\.o$$,.c,')
.SECONDEXPANSION:
%.o: $$(call Y,$$@)
    @echo "OBJECTS executed for $@ - [$^]"

src/foo.c:
    @echo "Congrats"

Once I run the above makefile, I get a circular dependency:

xxxx@null:~/Desktop/experiment$ make
make: Circular src.o <- src dependency dropped.
OBJECTS executed for obj/foo.o - [src/foo.c]
all executed

The reason is found quickly: Make creates the implicit rule Makefile: Makefile.o, which triggers my pattern rule. Since Makefile.o does not contain a slash, sed evaluates to src. Make again applies the implicit rule src:src.o which eventually causes the circular dependency. This can be seen in the make output:

make --print-data-base | grep src
make: Circular src.o <- src dependency dropped.
OBJECTS executed for obj/foo.o - [src/foo.c]
Y = $(shell echo $1 | sed -e 's,^[^\/]*,src,' -e 's,\.o$$,.c,')
# src (device 64769, inode 14031369): No files, no impossibilities so far.
Makefile.o: src
src: src.o
#  Implicit/static pattern stem: 'src'
src.o:
#  Implicit/static pattern stem: 'src'
# @ := src.o
# * := src
# < := src.o
obj/foo.o: src/foo.c
# + := src/foo.c
# < := src/foo.c
# ^ := src/foo.c
# ? := src/foo.c
src/foo.c:

If we now redefine Y in that way: Y = $(patsubst obj/%.o,src/%.c,$1), no circular dependency occurs, because make doesn't even try to apply the implicit rule Makefile:Makefile.o

make --print-data-base | grep src
OBJECTS executed for obj/foo.o - [src/foo.c]
Y = $(patsubst obj/%.o,src/%.c,$1)
# src (device 64769, inode 14031369): No files, no impossibilities so far.
obj/foo.o: src/foo.c
# + := src/foo.c
# < := src/foo.c
# ^ := src/foo.c
# ? := src/foo.c
src/foo.c:

When does make create the implicit rule Makefile: Makefile.o? What is the difference between the two different definitions of Y?

1

1 Answers

2
votes

The first thing is, if you're going to use secondary expansion it's your responsibility to ensure that the result is correct. In pattern rules that means your expansion has to handle all different values of %. Here your problem is fundamentally that you're not properly handling values of % that don't contain slashes, but your target %.o will apply to all targets, not just targets in a subdirectory. You need to fix your sed invocation. One option would be:

Y = $(shell echo $1 | sed -e 's,^[^\/]*/,src/,' -e 's,\.o$$,.c,')

By adding the slash that substitution won't match a simple filename. The replacement of the .o with .c will still happen however.

The reason you see this behavior is because make tries to remake makefiles. So it wants to build a target Makefile. There is a built-in pattern rule that knows how to build an executable from an object file with the same name: % : %.o The Makefile matches % so make tries to build Makefile.o. It sees your pattern rule %.o and tries to apply it and in the second expansion it converts Makefile.o to src as discussed above.

So, in addition to the above there are other things you can consider:

If you have a sufficiently new version of make (and you define all your own rules) you can disable all built-in rules by adding:

MAKEFLAGS += -r

This is a good idea regardless since it allows make to run much faster by removing all built-in rules.

Second, you could change your pattern to be more specific so it will only match object files, like this:

obj/%.o: $$(call Y,$$@)
        ...

Now, this pattern rule will only match .o files in that subdirectory.