gmake (GNU make - called simply make on linux systems) is a tool to help you build a program from its source. For our trivial Zoo program its possible to completely build the Zoo.exe from scratch in a few seconds. However, in HEP there are some very large programs made with a number of libraries with hundreds and possibly thousands of source files and then rebuilding from scratch can takes hours and is not practical during program development.
Having once built a program it isn't necessary to completely rebuild it after making some changes. Only things that are out of date need to be rebuilt and the rules are very simple:-
.o
file if its source has changed.
.o
files have changed.
However, applying such rules to a large software project would be both
tedious and error prone. Instead the gmake tool is used. You create
a special file called a makefile (the default name is
GNUmakefile
) which contains instructions on how to create all
object, library and executable files and then pass this file to gmake
along with a request to build the program. gmake applies the instructions
recursively and rebuilds just those parts that need to be rebuilt.
For example for our Zoo.exe program our GNUmakefile
file could
contain the following instructions for creating libZoo.so
:-
libZoo.so: Animal.o Dog.o Pig.o Human.o g++ -shared -o libZoo.so Animal.o Dog.o Pig.o Human.o
Breaking this apart:-
Again, our ZOO program is so trivial that the above is acceptable way to define how its library gets built, but what if a library consisted of a hundred object files? gmake can handle this with the use of macros (variables) and search/string utilities. We can write the above like this:-
LIB_SOURCES := $(wildcard *.cpp) LIB_OBJECTS := $(addsuffix .o, $(basename $(LIB_SOURCES))) libZoo.so: $(LIB_OBJECTS) g++ -shared -o $(LIB_OBJECTS)
*.cpp
files in the directory.
.cpp
) converted to
.o
libZoo.so
if any object file that comes from a .cpp
has changed.
When it comes to rules for compiling we don't write e.g.:-
Animal.o: Animal.c gcc -g Animal.c
we would have to write that many times in a big project. Instead we
define a rule that tells gmake how to create any .o
file from a
corresponding .cpp
one:-
\%.o:%.cpp g++ -g -c $< -o $@
.o
file from a .cpp
. The second line is
a rule where:-
$<
means the name dependency file (the .cpp
file in this case).
$<@
means the name target file (the .o
file in this case).
Now in writing the rule for recompiling object files we have missed out one very important fact: the code depends on any header files it includes. So if any of its headers have changed then the object file must be recompiled. Header files are a problem because they can include other headers which can include others and so on. So its not trivial to work out what headers any particular source file uses and even less trivial to maintain up to date dependencies in the makefile.
The solution to this problem comes in two parts. First a makefile can include a dependency without a rule. So we can write:-
Human.o: Human.h
which tells gmake that Human.o
depends on Human.h
but
does not tell gmake how to create Human.o
. You can have as
many dependency lines as you like; only one has to be followed by a
rule, the others just let gmake work out if the target needs to be
rebuilt.
The second part of the solution involves another utility called
makedepend. Its jobs is to take a list of source files and create a
list of dependency lines for the corresponding .o
files listing
all the headers, both directly and indirectly included in the source
file. makedepend can append its results back into the makefile
itself. To automate this process we add the following to the end our
makefile:-
depend: makedepend -f GNUmakefile $(LIB_SOURCES) # DO NOT DELETE THIS LINE -- make depend depends on it.
This introduces a new target depend
(that has no dependencies
which means that is is always considered to be out of date) that we
can ``build'' by typing:-
gmake depend
The rule tells makedepend to look though all the library sources,
create the dependency lines and add them to GNUmakefile
. Now
each time we make the depend
target we want to remove the
previous output and add the new lot. That's why the makefile ends
with that special comment line; makedepend looks for that and removes
anything after that before adding the new output.
The system isn't quite perfect, if we forget to build the depend
target
then the makefile will slide out of date. We can improve on this and
ensure that the dependency gets run every time by giving the rule to make
Zoo.exe
as:-
Zoo.exe: libZoo.so Zoo.cc depend g++ -g -o Zoo.exe Zoo.cc libZoo.so
This tells gmake to not only check on the library and the main program
source but to also run makedepend. This is now pretty good although
not quite perfect. Although makedepend updates the makefile,
gmake has already read it and does not go back and read it again. So
any change in dependency won't be incorporated until the next time
gmake is run on the file. Of course, if you know somethings has
changed you can always build the depend
target first. You can
also play clever tricks where gmake calls gmake again with the updated
makefile after running makedepend, but we have gone far enough in this
introduction.
One last point to demonstrate how flexible gmake is. You can use it to do the
reverse of building. It is typical to have a special target called clean
which we can define as:-
clean: rm -f *.o *.so *.a *.exe
so that typing:-
gmake clean
results in the directory is cleaned of all compiler and linker output.