Cooper Harasyn
Cooper Harasyn

Reputation: 143

Why does Make prefer the implicit rule over the rule listed in the Makefile?

I am trying to wrap my head around why Make is using a certain rule to compile my code. Here is a minimal example:

When running make I get the following:

$ make
gcc -O2 -Wno-implicit-function-declaration    main1.c   -o main1
/usr/bin/ld: /tmp/ccBsxlMV.o: in function `main':
main1.c:(.text.startup+0x11): undefined reference to `add'
collect2: error: ld returned 1 exit status
make: *** [<builtin>: main1] Error 1

My intent is to indicate through %: %.o $(obj_lib) that each target depends on all of the object files, so that the target's .o file will be linked with add.o. However, what I am observing instead is that Make is using the implicit rule for compiling a .c file to an executable, which fails since add.o isn't being linked in.

Why is Make using the implicit rule instead of the rule that I defined? How can I get Make to compile my targets using the command I specified there?

Upvotes: 0

Views: 718

Answers (2)

MadScientist
MadScientist

Reputation: 101081

The reason is that make always uses the shortest chain of implicit rules. So, if make can find a rule %.x -> %.y and another rule %.x -> %.z -> %.y, it will always choose the first one.

Make doesn't treat built-in and user-defined rules differently from each other, except that user-defined rules are searched first before built-in rules.

In your situation, you have a built-in rule that knows how to create a binary from a source file with the same name, and a rule you defined that knows how to create a binary from an object file and another chained rule that builds the object file from the source file. So make chooses the first one.

You can remove that built-in rule with:

%: %.c

as you did (you don't have to remove the %.o : %.c rule). Alternatively you can use a static pattern rule, which creates an explicit rule:

all: $(targets)

$(targets): %: %.o $(obj_lib)
        ...

Because this is a form of explicit rule, no implicit rule (like pattern rules) will be used.

Upvotes: 2

Cooper Harasyn
Cooper Harasyn

Reputation: 143

The other implicit rules are being used because they can. The .c file exists and so it can be used to build the executable.

There are multiple ways to fix this:

  1. Add the $(obj_lib) as a dependency of $(targets). This will cause Make to ensure that the library .o files are linked into the main executable, solving that problem. However, this can still use the implicit rule, so it does not address the 2nd part of the question.

  2. Prevent Make from using the implicit rules. This is the solution that I found that works best for my use-case. This can be done by adding the following to the end of the Makefile.

    # Delete implicit rules that could be used to create the executable.
    %: %.c
    %: %.o
    # Indicate that Make doesn't need to delete any intermediate objects. (Optional)
    .SECONDARY:
    

Here is a demonstration of this example before and after the change:

=============================================================
$ ls
Makefile  add.c  main1.c  main2.c  make.log
=============================================================
$ cat Makefile 
CC := gcc
CFLAGS := -O2 -Wno-implicit-function-declaration

targets := main1 main2
obj_targets := $(patsubst %,%.o,$(targets))

src_lib := add.c
obj_lib := $(patsubst %.c,%.o,$(src_lib))

all: $(targets)
%: %.o $(obj_lib)
        $(CC) $(CFLAGS) -static -o $@ $^

# .PHONY: all
# # Delete implicit rules that could be used to create the executable.
# %: %.c
# %: %.o
# # Indicate that Make doesn't need to delete any intermediate objects.
# .SECONDARY:
=============================================================
$ make
gcc -O2 -Wno-implicit-function-declaration    main1.c   -o main1
/usr/bin/ld: /tmp/ccBsxlMV.o: in function `main':
main1.c:(.text.startup+0x11): undefined reference to `add'
collect2: error: ld returned 1 exit status
make: *** [<builtin>: main1] Error 1
=============================================================
$ code Makefile 
=============================================================
$ cat Makefile 
CC := gcc
CFLAGS := -O2 -Wno-implicit-function-declaration

targets := main1 main2
obj_targets := $(patsubst %,%.o,$(targets))

src_lib := add.c
obj_lib := $(patsubst %.c,%.o,$(src_lib))

all: $(targets)
%: %.o $(obj_lib)
        $(CC) $(CFLAGS) -static -o $@ $^

.PHONY: all
# Delete implicit rules that could be used to create the executable.
%: %.c
%: %.o
# Indicate that Make doesn't need to delete any intermediate objects.
.SECONDARY:
=============================================================
$ make
gcc -O2 -Wno-implicit-function-declaration   -c -o main1.o main1.c
gcc -O2 -Wno-implicit-function-declaration   -c -o add.o add.c
gcc -O2 -Wno-implicit-function-declaration -static -o main1 main1.o add.o
gcc -O2 -Wno-implicit-function-declaration   -c -o main2.o main2.c
gcc -O2 -Wno-implicit-function-declaration -static -o main2 main2.o add.o
=============================================================
$ 

Upvotes: 0

Related Questions