How to avoid duplication in Makefile targets with similar recipes? - makefile

I have a Makefile which has a lot of targets and the recipe for each target is quite similar.
foo:
gcc foo.c -o foo
mv foo ~/bin
bar:
gcc bar.c -o bar
mv bar ~/bin
baz:
gcc baz.c -o baz
mv baz ~/bin
I would like to avoid all this duplication. I would like to have something like below (this is not valid syntax; this only expresses my intention).
TARGET_NAME:
gcc $(TARGET_NAME).c -o $(TARGET_NAME)
mv $(TARGET_NAME) ~/bin
Is it possible to do something like this? If not, what is the best Makefile I can write that can minimize duplication in recipes?

Your makefile is wrong because your targets (foo, bar, etc.) don't depend on their source files (foo doesn't depend on foo.c, etc.) So, changing the source code won't cause the target to be rebuilt.
Also, your makefile says you're creating a file foo, but your recipe actually creates a file ~/bin/foo, which is not the same thing.
Anyway, this is exactly what pattern rules are for:
EXES = foo bar baz
all: $(addprefix $(HOME)/bin/,$(EXES))
$(HOME)/bin/%:: %.c
gcc $< -o $#
(Thanks to Beta for pointing out my think-o in the original)

A make rule can actually match multiple targets:
foo bar baz:
gcc $#.c -o $#
mv $# ~/bin
However, you should make the dependencies explicit in the rules:
foo: foo.c
bar: bar.c
baz: baz.c
foo bar baz:
gcc $< -o $#
mv $# ~/bin
The first three lines only specifiy the dependencies without any actions to actually build them. You can generate these with the help of gcc: gcc -MM foo.c will print a rule for foo.c.

Related

How to define prerequisite for automatic variable in Makefile?

How to modify my Makefile to generate 1.bar, 2.bar and 3.bar by typing make all?
The problem is that all depends on $(bars) and it is empty unless I first run make foo.
foo:
touch 1.foo 2.foo 3.foo
bars = $(patsubst %.foo,%.bar,$(wildcard *.foo))
%.bar: %.foo
cp $< $#
all: $(bars)
You'll have to list the files *.foo in your makefile:
foos = 1.foo 2.foo 3.foo
foo:
touch $(foos)
bars = $(foos:.foo=.bar)
...
You have to have some starting point in your makefile. Make cannot infer the list of things to build starting from absolutely no information at all.
If you don't know what the output will be from a command the simplest way do it is with recursive make. Something like this:
all: geotiff.file
gdal_retile.py $<
$(MAKE) output
output: $(patsubst %.foo,%.bar,$(wildcard *.foo))
%.bar: %.foo
cp $< $#

why do echo work in %: foo.o rule but not in %: %.o?

This is my current makefile
.PHONY = all clean
all: foo
#echo "Dependencies: $<"
%: %.o
#echo "Checking.. $#, <- $<"
gcc -lm foo.o -o foo
#echo "\n"
%.o: %.c
#echo "Creating object.. $#, <- $<"
gcc -c foo.c
#echo "\n"
clean:
#echo "Cleaning up..."
rm -rvf foo.o foo
When I run make, it doesn't print out any echoed strings, but I still get the executable file. This is all the things that get printed to the terminal
gcc foo.c -o foo
echo "Dependencies: foo"
When I replace %: %.0 rule with %: foo.o, everything is printed to the terminal normally
Creating object.. foo.o, <- foo.c
gcc -c foo.c
Checking.. foo, <- foo.o
gcc -lm foo.o -o foo
Dependencies: foo
rm foo.o
In both cases, I still get the executable file foo and it works normally, but why do I get 2 different results in the terminal?
When I run make, it doesn't print out any echoed strings, but I still get the executable file.
Since you do not have an explicit rule for building foo, (GNU) make performs an implicit rule search, by which it attempts to find a chain of one or more pattern rules, whether user-provided or built-in, by which it can build foo. Although it could choose to apply your rule to build foo.o from foo.c and then your rule to build foo from foo.o, it has a shorter chain available: a built-in rule for building foo directly from foo.c. It chooses the latter because it's shorter.
When I replace %: %.0 rule with %: foo.o, everything is printed to the terminal normally
This is a bit of a quirk of the implicit rule search procedure. When you make that change, make identifies the revised rule as "applicable" for building foo on account of the only prerequisite having an explicit name (this is item 5.3 in the algorithm described in the manual). The built-in rule for building directly from %.c is also applicable, but the one given in the makefile has precedence (among rule chains of the same length). The fact that make has to figure out separately how to make foo.o doesn't enter into consideration in this case (this is the quirky part, but follows clearly from the docs).
Of course, this particular quirk is rarely triggered, because a rule of the form of your revised one is almost always inappropriate. It says that whatever target is to be built, it can be built from foo.o, via the provided recipe, but that recipe really works only for building foo. Instead of %: foo.o, then, you really ought to make it foo: foo.o:
.PHONY = all clean
all: foo
#echo "Dependencies: $<"
foo: foo.o
#echo "Checking.. $#, <- $<"
gcc -o $# $< -lm
#echo "\n"
%.o: %.c
#echo "Creating object.. $#, <- $<"
gcc -c foo.c
#echo "\n"
clean:
#echo "Cleaning up..."
rm -rvf foo.o foo
Additional notes:
link library options such as -lm should come at the end of the link line. The relative order of these and object files on the command line matters.
Avoid repeating yourself. Rules' recipes should use automatic variables where possible to avoid repeating the target or prerequisite names.

How to dynamically generate Makefile targets

Say I have a few files named foo.c, bar.c, baz.c, ... I want to create a Makefile target for each one that runs a build task.
foo:
make foo.c
.PHONY: foo
foo.c:
run build foo
bar:
make bar.c
.PHONY: bar
bar.c:
run build bar
...
I essentially just want to do make foo and it builds make foo.c. But I have x number of files and want to have make tasks for each. How to accomplish this. Something like:
FILES = $(foo bar baz ...)
$(FILES): $(FILES)
make $(FILE)
.PHONY: $(FILE)
$(FILES).c: $(FILES)
run build $(FILE)
But all the files are independent from each other.
You can use pattern rules for the .c targets and static pattern rules for the ones that have to be phony targets (i.e., foo and bar in your example):
targets := foo bar
.PHONY: $(targets)
# static pattern rule
$(targets): %:
make $*.c
# pattern rule
%.c:
run build $*

Given a target's name, get all of its prerequisites

If I have a makefile with e.g.:
foo.o: foo.c a.h b.h c.h
cc -c foo.c -o foo.o
Now, in some other part of the makefile, I want to get all the prerequsites of foo.o, like I'd do with $^ in the recipe. Something like:
$(info $(call GET_TARGET_PREREQS(foo.o))) # prints "foo.c a.h b.h c.h"
Basically, I have dependency files (generated by -M) for all my object files, and from there I want a list of all the header files that are included by a given object.
I'm hoping for a more or less pure make solution, and not a sed script that parses the *.d files and outputs makefile fragments.
If you want to print all prerequsites, you can always use $^
.PONY: all
all: a b c
#echo $^
a:
b:
c:

why make behaves different with quotes?

I know quotes are not supposed to be used within Makefile, but just out of curiosity, why make behaves differently with make foobar and make. See detailed code below.
Makefile:
TARGET = 'foobar'
$(TARGET): foobar.cpp
g++ -g $^ -o $#
clean:
rm foobar
output:
$ make
g++ -g foobar.cpp -o 'foobar' <-- correct
$ make clean
rm foobar
$ make foobar
g++ foobar.cpp -o foobar <-- incorrect but works. Why?
$ make clean
rm foobar
$ make baz <-- doesn't work, which is normal
make: *** No rule to make target 'baz'. Stop.
$
This only "works" because the shell is stripping the single quotes from your first example for you.
The quotes are literally in the value of the $(TARGET) make variable. make doesn't dequote the right-hand side of the TARGET = 'foobar' assignment.
You can see this by using $(info $(TARGET)) in your makefile.
So your target line:
$(TARGET): foobar.cpp
is creating a target with the name 'foobar' and not foobar like you expect.
This is why running make does the "right" thing and make foobar does something else.
make foobar is running the make built in rule for %: %.cpp.
The fact that your default 'foobar' target works to create foobar is because the shell sees the single quotes and strips them.
You'll notice that if you make make; make make will build your 'foobar' target twice but make foobar; make foobar will tell you there is nothing to be done the second time. That's because the first target creates a file different from what make is expecting.
If you were to quote $# in your recipe line you would see different behavior.
$(TARGET): foobar.cpp
g++ -g $^ -o '$#'
for example would have make run g++ -g foobar.cpp -o ''foobar'' and generate a foobar file while
$(TARGET): foobar.cpp
g++ -g $^ -o "$#"
would have make run g++ -g foobar.cpp -o "'foobar'" and generate a 'foobar' file (which would cause make; make to report nothing to be done for the second make run).
You want the quotes in the recipe line not in the variable here.
TARGET = foobar
$(TARGET): foobar.cpp
g++ -g $^ -o '$#'
clean:
rm foobar
That being said since you can't have spaces in make target names (not reliably at least) the need for those single quotes (or any quoting) is diminished since you only need it if the filename contains shell metacharacters.
It's because make uses a default rule when it does not find specific rules to build a target.
You can compile program from program.cpp even without or with an empty Makefile. Try
make -f /dev/null foobar
The default rules are specified by POSIX and your make implementation has probably some of its own.
Trying to build baz fails, because none of the default rules knows how to build a baz.c or baz.cpp or any of the other built-in source files that could be used to build baz.

Resources