Makefiles, include function

Asked

Viewed 257 times

8

I’m trying to analyze the makefile below. But I don’t understand how the bottom fits with the rest.

%.d: %.cpp
<TAB> g++ $< -MM -MT '$*.o $*.d ' -MD $(CPPFLAGS)

creates a file .d containing a type of information

   Fraction.o: Fraction.cpp Fraction.h

ie the file to compile with its dependencies.

Comes after the

-include $(CPPSOURCES:.cpp=.d)

which will include the previous line in makefile

Great, this will show the dependencies of all cpp files to compile. But no commands are associated.

Compile command comes with target:

 %.o: %.cpp
 <TAB> g++ -c $< $(CPPFLAGS) -o $@

How this target is associated with include?

Here the complete code:

CPPUNIT_PATH=/diretorio_onde_esta_o_cppunit
INCLUDE_DIR=$CPPUNIT_PATH)/include
LIB_DIR=$(CPPUNIT_PATH)/lib
LIBS=-lcppunit

CPPFLAGS=-I$(INCLUDE_DIR)
LDFLAGS=-L$(LIB_DIR) $(LIBS)

CPPSOURCES = $(wildcard *.cpp)

all: ftest

ftest: $(CPPSOURCES:.cpp=.o)
<TAB> g++ -o $@ $^ $(LDFLAGS)

%.o: %.cpp
<TAB> g++ -c $< $(CPPFLAGS) -o $@

clean
<TAB> -rm -f *.o ftest *~

remade:
<TAB> $(MAKE) clean
<TAB> $(MAKE)

-include $(CPPSOURCES:.cpp=.d)

 %.d: %.cpp
 <TAB> g++ $< -MM -MT '$*.o $*.d ' -MD $(CPPFLAGS)

1 answer

2

tl;dr

  1. makefile makes the expansion of your variables and commands working with text replacement;
  2. included files can be generated again and in this case read the makefile is restarted;
  3. a target can have multiple dependencies, including dependencies written at different times;
  4. 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:

  1. the file does not exist, but there is a creation rule for it;
  2. the file does not exist, nor is there any rule of creation for it;
  3. the file exists, but there is a rule for it indicating that it is outdated;
  4. the file exists, and it is updated according to the rule;
  5. 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 makefileis 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:

  1. the generation rule is used for Fraction.d, because it is an included file and therefore dependency on this makefile current;
  2. 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.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.