Define a choice of prerequisites in a pattern rule - makefile

For example, lets say I have a compiler that can build foo files from either bar or baz sources.
The rules for this might look like:
%.foo: %.bar
# commands to
# invoke compiler
%.foo: %.baz
# commands to
# invoke compiler
However, this could start getting a bit long and redundant as the number of input types and recipe commands increase. Is there any syntax available to compress this into a single rule?
%.foo: $(oneof %.bar %.baz)
# commands to
# invoke compiler

What you propose at the beginning is right: Makefiles should be clear and concise regarding building rules.
In the other hand you may take a look at Canned Recipes to try to avoid repeating the same recipes once and again:
define MAKE_FOO =
#You may use automatic variables such as $^ or $#.
mv $< $# #In this example just a file renaming.
endef
%.foo: %.bar
$(MAKE_FOO)
%.foo: %.baz
$(MAKE_FOO)
The canned recipe MAKE_FOO will expand to whatever recipes you write inside the define statement as if they were copied manually.

Here's an illustration for the concrete problem of making an .o file
from either a .c file or a .cpp file with a combined pattern rule.
An executable is also built to aid the illustration.
Makefile
.PHONY: all clean
all: test
%.o: %.c %.cpp
gcc -c $?
test: main.o hw.o
g++ -o $# $^
clean:
rm -f test *.o
where we have:
hw.c
#include <stdio.h>
void hw(void)
{
puts("Hello from C");
}
hw.cpp
#include <iostream>
extern "C" void hw()
{
std::cout << "Hello from C++" << std::endl;
}
and:
main.cpp
extern "C" void hw(void);
int main(void)
{
hw();
return 0;
}
Make from clean and run:
$ make clean && make && ./test
rm -f test *.o
g++ -c -o main.o main.cpp
gcc -c hw.c hw.cpp
g++ -o test main.o hw.o
Hello from C++
Both hw.c and hw.cpp were compiled per the pattern rule.
Each one of them was compiled to the same object file, hw.o, with the second, C++
compilation overwriting the C compilation. So the C++ object file was linked,
simply because it was the last to be built. Be clear about what you expect to
happen when the combined rule is triggered by multiple prerequisites.
Now let's update hw.c and repeat:
$ touch hw.c
$ make && ./test
gcc -c hw.c
g++ -o test main.o hw.o
Hello from C
This time, hw.o was compiled only from hw.c, and linked.
Update hw.cpp and repeat:
$ touch hw.cpp
make && ./test
gcc -c hw.cpp
g++ -o test main.o hw.o
Hello from C++
Once again, the hw.o from C++ was linked.
The key element of the combined pattern rule is $?, which
means all the prerequisites that are newer than the target

Related

why does g++ add includes differently within makefile?

I am new to make files and having an issue where the g++ command works when run on the command line but not within a make file. The following line works:
g++ -I/home/user/ml/dynet/dynet -I/home/user/ml/dynet main.cpp
but the following make file can't find the required header file
ai: main.o
g++ -I/home/user/ml/dynet/dynet -I/home/user/ml/dynet main.cpp
the main.cpp is simple
#include <iostream>
#include "dynet.h"
int main() {
std::cout << "Hello World!";
return 0;
}
when I run the make I get the error that it can't fine the
user#LAPTOP-AUOAPRL1:~/ml/ai$ make
g++ -c -o main.o main.cpp
main.cpp:3:10: fatal error: dynet.h: No such file or directory
#include "dynet.h"
Why would the same line run differently? where is it looking when run within make? Thank you
GNU Make in it's infinite wisdom has chosen to use it's built-in rules. You can run without them by make --no-builtin-rules. It's not nice to have that as a prerequisite for building your files, but it might be useful when debugging "strange behavior" as it proves whether it's the built-in rules interfering with your mental model or not.
As for the wisdom itself, you only say how to build ai from main.o - not how to build main.o. That is what you're seeing - the built-in rule for building main.o.
What I think you want to do instead is have the rule like this:
ai: main.cpp
g++ ...
A tip; you should prefer using the built-in rules and variables, as it makes the makefile a little simpler to manage:
# using built-in variables
CC := g++
CPPFLAGS := -I/home/user/ml/dynet/dynet -I/home/user/ml/dynet
# inspired by built-in rules
ai: main.o
$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $#
See https://www.gnu.org/software/make/manual/html_node/Catalogue-of-Rules.html

How to make multiple targets by the same rule using target-dependent compilers?

Suppose that I would like to verify the compatibility of hello.c with multiple compilers. How to do it using a Makefile?
Here is a Makefile I write for this purpose.
# Makefile, version 1.
# It tests hello.c using multiple compilers.
TEST = test_gcc test_clang
.PHONY: $(TEST) all
all: $(TEST)
test_gcc: CC = gcc
test_clang: CC = clang
$(TEST): hello
./hello
rm -f hello
hello: hello.c
$(CC) hello.c -o hello
If I run make test_gcc or test_clang, everything works. However, make all leads to the following.
./hello
Hello, world!
rm -f hello
touch hello.c
./hello
makeĀ : ./hello : command not found
make: *** [Makefile:10 : test_clang] Error 127
So hello is not remade for test_clang. This seems to a Makfile beginner like me.
Question: In my Makefile, test_clang depends on hello, which has been removed when test_gcc is finished. So why doesn't make generate it again before running ./hello ?
My Attempts:
To solve the problem, I tried the following modification, which touches hello.c after making test_gcc or test_clang. It still does not work, the problem being the same.
# Makefile, version 2.
# It tests hello.c using multiple compilers.
TEST = test_gcc test_clang
.PHONY: $(TEST) all
all: $(TEST)
test_gcc: CC = gcc
test_clang: CC = clang
$(TEST): hello hello.c
./hello
rm -f hello
touch hello.c
hello: hello.c
$(CC) hello.c -o hello
Following the advice of #HolyBlackCat, I tried also the following.
# Makefile, version 3.
# It tests hello.c using multiple compilers.
TEST = test_gcc test_clang
.PHONY: $(TEST) all
all: $(TEST)
test_gcc: CC = gcc
test_clang: CC = clang
$(TEST): hello_$(CC)
./hello_$(CC)
rm -f hello_$(CC)
hello_$(CC): hello.c
$(CC) hello.c -o hello_$(CC)
The output of make all is
gcc hello.c -o hello_gcc
./hello_gcc
Hello, world!
rm -f hello_gcc
./hello_clang
makeĀ : ./hello_clang : commande not found
make: *** [Makefile:13 : test_clang] Error 127
This is even stranger --- hello_clang is never made even though it is required (only) by test_clang.
For your convenience, here is the standard hello.c I used for the test.
/* hello.c */
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
Thank you very much for any suggestions. It will also be appreciated if you comment on my Makefiles in general, not necessarily regarding the question I raised. I am really a beginner.
See the documentation for target-specific variables: it's quite clear that target-specific variables take effect only in recipes. You cannot use them in prerequisites, or of course when defining targets.
It's probably simpler to do this without target-specific variables and just use pattern rules instead:
TEST = test_gcc test_clang
.PHONY: all
all: $(TEST)
test_%: hello_%
./$<
hello_%: hello.c
$* $< -o $#
(I don't know why you're removing the binary immediately after you test it, in this version, so I removed that).

Using Makefile to run multiple programs

Whenever I try to run this, the only output I get is "make: foo.o is up to date." It seems as if the rest of the program does not run and I do not know why. My instructions are as follows: "Compile a C program. Run a C program. Run a Python program. Compile and run a java program. Check for a README, display it. Compare 2 files. Clean up intermediary files."
cc = gcc
EXE = foo
JAVAC = javac
JRE = java
PAGER = less
TEST_OUT = test.out
EXP_OUT = expected.out
foo.o: foo.c foo.h
$(cc) -c foo.c
main.o: main.c foo.h
$(cc) -c main.c
$(EXE): foo.o main.o
$(cc) -o$(EXE) main.o foo.o
run-c: $(EXE)
./$(EXE)
run-py:
./foo.py
read: README
$(PAGER)
foo.class: foo.java
$(JAVAC) foo.java
run-java: foo.cass
$(JRE) foo
save-java:
./(run-java) >> $(TEST_OUT)
test-java: $(TEST_OUT) $(EXP_OUT)
#if diff $(TEST_OUT) $(EXP_OUT) &> /dev/null ; then \
echo "Passed!" ;\
else \
echo "Not the same!" ;\
fi
clean:
-rm test.out
Whenever I try to run this, the only output I get is "make: foo.o is up to date."
By default, make runs the topmost rule when no target is specified. You have to run for example make run-c to invoke a corresponding recipe, or you can just put an all rule before any others which depends on and does all the things.
read: README
$(PAGER)
I suspect you might have missed putting $# after $(PAGER) as the argument.
save-java:
./(run-java) >> $(TEST_OUT)
You can't just "include" other recipes this way. Instead, repeat what's in run-java and append the redirection.
If you want to specify "pseudo" targets, I recommend you to specify them as .PHONY, such as:
.PHONY: all run-c run-py run-java save-java test-java clean
To mark some targets as intermediate files, use the .INTERMEDIATE directive. GNU Make manual (texinfo) is available both online and via the info command.

GNU make seems to ignore non-terminal match-anything rules for intermediate files

I have the following files in a directory:
FP01.c:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
long double ld = 0.1L; // long double constant (L or l suffix)
scanf("%Lf", &ld);
return 0;
}
makefile:
MAKEFLAGS += -rR
# the default target
.PHONY: all
all: FP01.elf
%.elf: %
cp $< $#
# prevents non-terminal match-anything rules from matching target '%.c'
# see section 10.5.5 of GNU make manual
%.c:
# a non-terminal match-anything rule
%: %.c
gcc -Wall -g $< -o $#
If FP01 does not exist, running make gives the following output:
make: *** No rule to make target 'FP01.elf', needed by 'all'. Stop.
However, if I run the following commands before make, then everything works as expected:
$ touch FP01
$ touch FP01.c
$ make
gcc -Wall -g FP01.c -o FP01
cp FP01 FP01.elf
Am I missing something or there is a bug in GNU make?
make --version gives the following output:
GNU make 4.1
Built for i686-pc-linux-gnu
Copyright (C) 1988-2014 Free Software Fundation, Inc.
License GPLv3+: ...
...
EDIT:
It seems that making match-anything rule terminal somehow fixes the problem, but I want to use built-in rule to generate FP01 if possible and unfortunately it is non-terminal.
Another thing is that I believe that non-terminal rule should work so using terminal rule doesn't actually solve the problem as I still don't know whether the bug is in make or in my "mental makefile parser".
I'm not sure if this is a bug or not, I'd have to look more deeply into it. But the simple way to fix your problem is to set the match-anything pattern rule that compiles from the .c file as terminal, which is how it should be (unless you're generating the source files from somewhere else):
%:: %.c
cp $< $#
$ make
cp FP01.c FP01
cp FP01 FP01.elf
rm FP01
I think you should use :: on the % : %.c rule because you actually want that to be a terminal match-anything rule (since you don't need the .c file to be built):
MAKEFLAGS += -rR
.PHONY: all
all: FP01.elf
%.elf: % ; cp $< $#
% :: %.c ; gcc -Wall -g $< -o $#
Note that I used the ; form for the recipes here because it's easier to copy and paste because no need to worry about tabs.
Adding FP01 as a prerequisite of .INTERMEDIATE (special built-in target) seems to make it work (no need to modify the match-anything rule). Just another workaround.
MAKEFLAGS += -rR
# the default goal
.PHONY all
all: FP01.elf
%.elf: % ; cp $< $#
# prevents non-terminal match-anything rules from matching target '%.c'
# see section 10.5.5 of GNU make manual
%.c:
# a non-terminal match-anything rule
%: %.c ; gcc -Wall -g $< -o $#
.INTERMEDIATE: FP01

Why automatic variable in my makefile doesn't find any target?

I've written this hello world in hello.c:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
exit( 0 );
}
my Makefile is:
%: %.c
When I run make I will get this error: make: *** No targets. Stop.
Your makefile provides a rule %: %.c specifying that it's extensionless executables and .c files that you're interested in (in fact, just the built-in rules do that much), but gives no hint that there's a source file named hello.c or a target named hello.
When you type make by itself, make takes the first target listed in the makefile as the target to be made, but your makefile contains no targets whatsoever, hence No targets. Stop. In short, make has no clue that there is anything nearby with a name like hello*.
With your makefile as is, typing make hello will do what you want, as it tells make what it is that you'd like to build.
If you tell make about hello, you'll also be able to type just make to do what you want:
hello: hello.c
%: %.c
or more idiomatically and flexibly, you would list all your "top-level" targets in an all target:
all: hello
%: %.c
.PHONY: all
Typical c hello world prgram:
hello.c:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
Typical Makefile, short but complete:
Makefile:
all: hello
hello: hello.o
gcc -o "$#" hello.o
hello.o: hello.c
g++ -c hello.c
.PHONY:clean
clean:
rm *.o hello
Example with pattern rules:
all: hello
hello: hello.o
gcc -o "$#" hello.o
%.o: %.c
gcc -c $<
.PHONY:clean
clean:
rm *.o hello
Example with delimiter(\n as enter, \t as tab):
all: hello\n
hello: hello.o\n
\tgcc -o "$#" hello.o
%.o: %.c\n
\tgcc -c $<
.PHONY:clean
clean:\n
\trm *.o hello

Resources