tl;dr
makefile
makes the expansion of your variables and commands working with text replacement;
- included files can be generated again and in this case read the
makefile
is restarted;
- a target can have multiple dependencies, including dependencies written at different times;
makefile
it’s magical, sometimes too much, and it’s confusing ;-)
An example of item 3, which indicates that Fraction.o
depends at the same time on Fraction.cpp
and Fraction.h
:
Fraction.o: Fraction.cpp
<TAB> g++ -c $< $(CPPFLAGS) -o $@
Fraction.o: Fraction.cpp Fraction.h
First of all, I’d like to say that I knew the include file
of makefile
, but this -include file
intrigued me.
The include
without the dash in front forces the makefile
to insert, ipsis Litteris, the contents of the file inside the makefile
current. Obviously this causes trouble if the file does not exist (and it is not possible to create it), then the make
explodes in catastrophic fashion.
Already the -include
works almost the same as include
, the only difference is in the absence of the included file (and in the inability to generate it again as well). In this case, the -include
passes silently without warning errors. More details can be found in documentation of Include of GNU Makefile.
Then we go to the mystery of the archives .d
. Second note in documentation of GNU Makefile, files .d
are files makefile
normal. In this case and in more mundane cases, the .d
is a makefile
dynamically generated that is updated on demand, containing implicit dependency rules. Note that you put the way it creates these files in its own example:
%.d: %.cpp
<TAB> g++ $< -MM -MT '$*.o $*.d ' -MD $(CPPFLAGS)
Here, you’re saying any file that ends in .d
can be generated from a file of the same prefix but extension .cpp
.
Now comes the magic. You need to compile to an object file .o
any source .cpp
. I don’t particularly remember how the makefile
handles multiple rules of creation, but I remember how it handles dependencies.
I’ll just focus here on the archives Fraction
, ignoring others for now.
At first, makefile
will read your file and expand all variables and commands it finds (and is possible). I’ll ignore some variables that are specific to you, like $(INCLUDE_DIR)
, $(LIBS)
and so many others. So the first expansion that he will worry about is CPPSOURCES = $(wildcard *.cpp)
. As we are now focusing only on Fraction
, this will expand to CPPSOURCES = Fraction.cpp
. Until then, there is no dependency line in your makefile
, only variables.
Then he finds the first rule: all: ftest
. So, effectively, your makefile
at that point has the following content:
all: ftest
A target .PHONY
, that will not be generated a file with its name, but has an identifier to help when performing the build.
The next line with content is ftest: $(CPPSOURCES:.cpp=.o)
. Note that the variable CPPSOURCES
is experiencing an expansion in which the string .cpp
is replaced by the string .o
. As its content at this time is Fraction.cpp
, this expansion returns Fraction.o
. The current state of makefile
is:
all: ftest
ftest: Fraction.o
<TAB> g++ -o $@ $^ $(LDFLAGS)
The special automatic variable $@
expands to the target, so when rotating the command it will be called ftest
; already the special automatic variable $^
expands to all direct dependencies of ftest
, which in our case is Fraction.o
; note that by having more than one file .cpp
, the variable CPPSOURCES
will expand to multiple values .o
in dependence on ftest
. Because they are special variables that depend only on the target
and of dependencies
, I will leave here. More about automatic variables in documentation of GNU Makefile
Then there is the rule %.o: %.cpp
, that transforms a file .cpp
in an archive .o
.
%.o: %.cpp
<TAB> g++ -c $< $(CPPFLAGS) -o $@
Here is the use of the special automatic variable $<
. Other than $^
, only the first dependency is used. Therefore, no matter what other dependencies there are to generate the file .o
, will only be replaced by $<
the file name .cpp
. So we have that your file now contains 3 dependencies, with two creation rules:
all: ftest
ftest: Fraction.o
<TAB> g++ -o $@ $^ $(LDFLAGS)
%.o: %.cpp
<TAB> g++ -c $< $(CPPFLAGS) -o $@
Then enter two more targets .PHONY
before arriving at the -include
:
all: ftest
ftest: Fraction.o
<TAB> g++ -o $@ $^ $(LDFLAGS)
%.o: %.cpp
<TAB> g++ -c $< $(CPPFLAGS) -o $@
clean:
<TAB> -rm -f *.o ftest *~
remade:
<TAB> $(MAKE) clean
<TAB> $(MAKE)
Here, then, the makefile
enters the inclusion line of the other makefile
. Files are given by expansion $(CPPSOURCES:.cpp=.d)
. In our case, this expansion generates Fraction.d
. So we have to makefile
will interpret the following command: -include Fraction.d
. Here things get interesting.
In this case, there are 5 possible alternatives:
- the file does not exist, but there is a creation rule for it;
- the file does not exist, nor is there any rule of creation for it;
- the file exists, but there is a rule for it indicating that it is outdated;
- the file exists, and it is updated according to the rule;
- the file exists, but there is no rule for it.
So far, it is not possible to distinguish if the file is in the alternatives 1,2
or if you are in 2,3,4
, because we didn’t reach the end of the file and we didn’t find any rules of creation for it. Let’s assume at the moment that the file exists, and that some change was made in Fraction.cpp
. At this point, with this assumption, the content of makefile
is as follows:
all: ftest
ftest: Fraction.o
<TAB> g++ -o $@ $^ $(LDFLAGS)
%.o: %.cpp
<TAB> g++ -c $< $(CPPFLAGS) -o $@
clean:
<TAB> -rm -f *.o ftest *~
remade:
<TAB> $(MAKE) clean
<TAB> $(MAKE)
Fraction.o: Fraction.cpp Fraction.h
Soon after, it finds a rule to update files .d
, %.d: %.cpp
. As we stand on the assumption that the file .cpp
is more up-to-date than the .d
. In this case, the following steps will happen:
- the generation rule is used for
Fraction.d
, because it is an included file and therefore dependency on this makefile
current;
- the
makefile
is read again, from scratch, from the beginning.
If the file Fraction.d
did not exist a priori, it would also follow the same steps of creating the file and read again the makefile
. The more you find in documentation of GNU Makefile.
After the file is regenerated, it will follow all the reading steps and expansions of makefile
again, until finally it ends in the following format:
all: ftest
ftest: Fraction.o
<TAB> g++ -o $@ $^ $(LDFLAGS)
%.o: %.cpp
<TAB> g++ -c $< $(CPPFLAGS) -o $@
clean:
<TAB> -rm -f *.o ftest *~
remade:
<TAB> $(MAKE) clean
<TAB> $(MAKE)
Fraction.o: Fraction.cpp Fraction.h
%.d: %.cpp
<TAB> g++ $< -MM -MT '$*.o $*.d ' -MD $(CPPFLAGS)
Here, the archive Fraction.d
is more up-to-date than Fraction.cpp
, because it has just been generated, so he will not need to worry again about the creation of the Fraction.d
. From here on, the makefile
will perform the constructions; until then he was just preparing.
I am assuming that you have not passed any parameter and that there is no file called all
in your working directory (to prevent a file called all
prevent its compilation, read on targets.PHONY
). all
depends on ftest
and nothing else.
To execute ftest
, you need to Fraction.o
.
There is an implicit creation rule for Fraction.o
and in addition there is also another dependency declared for it. Altogether, the set dependencies of Fraction.o
is the union of the declared dependencies; in this case, we have Fraction.o: Fraction.cpp Fraction.h
and %.o: %.cpp
(which expands into Fraction.o: Fraction.cpp
), hence the union is Fraction.o: Fraction.cpp Fraction.h
(not accepted repeats in the union). Therefore, given that the file Fraction.cpp
or the Fraction.h
be updated, the file Fraction.o
needs to be generated.
There are no rules of creation nor to Fraction.cpp
nor to Fraction.h
, so the file Fraction.o
is already ready to be generated with the command g++ -c Fraction.cpp $(CPPFLAGS) -o Fraction.o
.
Having all its dependencies generated, ftest
is ready to be generated with the command g++ -o ftest Fraction.o $(LDFLAGS)
.
After ftest
be generated, there is no more dependency to all
, so the target was successfully generated.