A simple answer is make's behavior. But it doesn't give understanding how it works, so let's go deeper.
We begin with the first Makefile. Because my directory has only one file fizzbuzz.s, the variable BIN will be equal fizzbuzz. Make searches the fizzbuzz target, but there is no the target. For that reason make processes built-in rules. It searches files with extension added to fizzbuzz target and if it has searched the files it processes own rules. I tried to remove the Makefile and run command: make fizzbuzz and it runs another
cc fizzbuzz.s -o fizzbuzz.
It's equal to the first Makefile.
Now, we'll use the second Makefile.
When I changed the .s extension on .asm it worked. Interesting behavior, it's because make doesn't have built-in rules for .asm extension. I wanted to get understanding so I returned .asm to .s back.
My second Makefile has a rule for target without extension:
%: %.o
$(LNK) -melf_i386 $< -o $@
But it doesn't work as expected and works like without Makefile. This happens, I suppose, because % has the last priority execution after built-in rules. We can change the behaviour by adding MAKEFLAGS += --no-builtin-rules in the Makefile, and everything works fine.
But I noted one weird thing without the option. I did the command make fizzbuzz.o and it has been built fizzbuzz.o(it processes command from my Makefile). So, OK. Let's run make again. I run it and it built me the final fizzbuzz target. Surprise!
So the rule
%: %.o
$(LNK) -melf_i386 $< -o $@
is working when there are object files(.o) in the current directory. I don't know what it is, a bug or a feature(IMHO a weird feature), but it works so.
Finally, we have two solutions to make second Makefile working and they are the following:
- omit built-in rules by replacing
.s extension by .asm or any other which doesn't have built-in rules in make
- disable built-in rules by adding to Makefile an option
MAKEFLAGS += --no-builtin-rules or run with make -R or make -r