Make and makefiles - a commented example to help you learn
Archive - Originally posted on "The Horse's Mouth" - 2010-03-12 15:30:46 - Graham EllisIf you're making an executable C++ file you build it from a whole lot of .o files, each of which you have compiled from a .cpp file. And you also build in library files. If you change a .cpp file, then you have to recompile it into a .o ... but don't forget that you also have to recompile the .cpp if you have changed any of the header files it includes. And the final build from .o to executable can be a long statement.
You might well say "oh - for goodness sake - put all the compile and build instructions in a batch file / shell script and run that when you change anything" and that can be quite effective for a small application but as the application grows:
• You won't want to recompile everything every time and
• You won't want to duplicate large numbers of similar compile instructions into your script.
This is where a Makefile comes in to play. A Makefile contains a series of instructions of the form "If file xxx does not exist, or is older than file yyy [from which it is created] the run the following instruction to (re)create file xxx". And the make utility applies this whole setup of dependecnises to work out what needs to be done, and goes off and does it.
For the straightforward Multiple Inheritance in C++ Example that I wrote about earlier, I provided a simple Makefile [here]. It contains directives of the sort:
Expense.o: Expense.h Expense.cpp
g++ -c Expense.cpp
which says that if Expense.cpp or Expense.h have changed more recently that Expense.o, you need to (re)run the gcc line that follows.
There are, though, many short cuts you can use in a Makefile to make it more compact, easier to edit, and to reduce duplication in it. Unfortunately, they make it far harder for the uninitialted to read the file. And it seems that this is a technology which Geeks tend to keep - "Secret Squirrel" to themselves; comments in makefiles are about as rare as daffodils in September!
I have taken the Makefile for the C++ multiple inheritance demo, and I have added in many of the more advanced techniques to show what can be done - and I've added comments too. You can find the complete Makefile [here] ... and you might like to note:
• I can define Macros with assignments
HEADERS=Film.h HireFilm.h Expense.h iostream
which I can then use further down my Makefile with a $(....) notation
Filmtest.o: Filmtest.cpp $(HEADERS)
This is commonly done not only for batches of header and object files, but also for the name of the C compiler and flags to the compile and load - typically CC=, CFLAGS= and LFLAGS=.
• As a special case, I can ask Make to look in a path for dependencies using VPATH
VPATH=/usr/include/c++/4.1.0
and I can add other directories for files who's names match a pattern using vpath [lower case]:
vpath %.txt ../docs
will say that make should look into the sibling docs directory for .txt file (note the use of % as a wild card in makefiles, rather like in MySQL).
• I can take a list of file names of one particular type and turn it into a list of file names of a different type:
HIGHSOURCE=Filmtest.cpp HireFilm.cpp
HIGHOBJ=$(HIGHSOURCE:%.cpp=%.o)
Defines HIGHSOURCE as being some source files, and HIGHOBJ as being the associated object files. Just add to the source list and you automatically add the the object list!
• The SUFFIXES rule(s) allow you to create a set of generic rules telling make how to turn one set of files (by extension) into another. First define the suffixes:
.SUFFIXES: .html .txt
then the rules:
.txt.html:
@echo "<html><body>" > $@
@cat ../docs/$< >> $@
@echo "</body></html>" >> $@
@echo "Web Page $@ created"
So in that example, if you ask the system to make a .html it will top and tail a .txt file from the .docs directory with html headers and footers. In this example,also note:
* @ to start a command means "do not echo this instruction"
* $< is used to refer to the incoming file for the conversion
* $@ is used to refer to the outgoing or target file.
• The first target in the file will be the one to be made if you don't give make any parameters, and if that target isn't a file name, the target will always be run.
all: Filmtest
@echo "The whole caboodle is now up to date"
In this case, with no file called "all", the code will always be run and you'll get the message
• You can define your own rules that are going to be used for most conversions of particular cases - here's a rule that shows how most .o files are made from .cpp files:
%.o: %.cpp %.h
$(CC) -c $(CFLAGS) -o $@ $<
Although that's a fallback rule and so it will be overridden where you provide an explicit rule.
• Finally, it's common to provide a dummy target "clean" to delete all temporary and interim files so that you can force a rebuild from scratch:
clean:
@-rm *.o
@-rm Filmtest
@-rm *.html
@echo "Clean dup"
In this case, the extra "-" sign tells make to carry on even if there was an error!
Here is an example of that makefile being run:
[trainee@easterton 007]$ make
g++ -c -O2 -pg Filmtest.cpp
g++ -c -O2 -pg HireFilm.cpp
g++ -c -O2 -pg -o Film.o Film.cpp
g++ -c -O2 -pg -o Expense.o Expense.cpp
g++ -o Filmtest -pg Filmtest.o HireFilm.o Film.o Expense.o
The whole caboodle is now up to date
[trainee@easterton 007]$ make
The whole caboodle is now up to date
[trainee@easterton 007]$ make about.html
Web Page about.html created
[trainee@easterton 007]$ make about.html
make: `about.html' is up to date.
[trainee@easterton 007]$
(I have added the -pg option since I uploaded the sample make file to the web site, as I was demonstrating how to set up and use tools such as the gprof profiler and the indent code tidy utility, but those are probably subjects for another day.)
There are many more makefiles in our sample directories - - - - - - - - - - - - - - - - - -. Each dash links to a different one and by adding links here our web site software automatically adds links back to this page ;-)