Section 15 of the Art of Unix Programming is also all about Make. Read it!
library
, then I could type make
libarary
and, without recompiling your main file or
relinking your program, would at least compile the files that
make up your library and package them up into your .a and/or
.so file.
When you've got this working, I should be able to go to your
website, download a tar file, untar it in a directory, type
make
and have your libararies and your program
created for me.
make
utility is supposed to automate the
process of compiling and linking programs. I could go on for
hours about it, but I just want to get some of the down and
dirty details out of the way: the main motivation for make
involves incremental compilation in large progams and
libraries. Since we're still looking at relatively small
scale stuff, that motivation isn't there. Just keep in mind,
that make's true power is with larger software systems, and
we're getting there.
Consider the program cth ("Coint Toss Histogram"), which is my first crack at your previous homework. It simulates my stupid coin toss game being played 100,000 times, and produces a histogram of the result --- either as an eps-file or as a window popping up on the screen. It uses the libraries from all your projects.
Once Mr. Cole & Mr. Addison fix pp.h
,
compiling and linking cth will be as simple as
g++ -c cth.cpp -I/home/m062562 -I/home/m053348 -I/home/m051038 -I/home/m056654 -I/home/m051284 g++ -o cth cth.o /home/m062562/libFreq.a /home/m053348/scaler_lib.a /home/m051038/librz.a /home/m056654/epslib.a /home/m051284/libpp.a -L/usr/local/glut-3.7/lib/glut -lX11 -lXmu -lGL -lGLU -lglutWhat could be easier, right? Well, clearly typing this in fresh everytime I forget a semi-colon is going to be a pain in the neck. A rational response would be to simply collect those compilation commands into a file and run it as a script. The snazzier response, showing a bit of the programmer's aesthetic, would be to write the shell script so that the different componants of these two commands were stored in variables, so that the whole thing was a little easier maintain. Something like this:
cscript #!/usr/bin/tcsh # The variables INCLUDES contains the compiler flags that will add each student's # directory to the path the compiler uses to search for header files. set INCLUDES = "-I/home/m062562 -I/home/m053348 -I/home/m051038 -I/home/m056654 -I/home/m051284" # The variable STULIBS contains all the student libraries the program links to. # The backslash at the end of each line jus means "continue on next line as if # no line break were there". set STULIBS = "/home/m062562/libFreq.a /home/m053348/scaler_lib.a /home/m051038/librz.a /home/m056654/epslib.a /home/m051284/libpp.a" # The variable GPHCSLIBS contains all the compiler flags to link to the X, OpenGL # and Glut libraries the program requires set GPHCSLIBS = "-L/usr/local/glut-3.7/lib/glut -lX11 -lXmu -lGL -lGLU -lglut" g++ -c cth.cpp ${INCLUDES} g++ -o cth cth.o ${STULIBS} ${GPHCSLIBS}
Hopefully you realize that this is just the lines I typed
into the shell to compile it in the first place, just with
certain sections broken out into variables.
The variable INCLUDES
stores all the
flags that tell the compiler where to look for the student
header files. The variable STULIBS
stores all
the student libraries. The variable GPHCSLIBS
stores all the flags the compiler needs to find the X,
OpenGL and Glut libraries that the pp
library
relies on to produce its pretty graphics window.
At its simplest level, a Makefile is just slightly different
way to package up the same thing. Once again, at its
simplest level the only thing that changes is that
you can dispense with the set
keyword and the
quotes when you're defining your variables, and the actual
commands to compile are packaged up a little differently.
Here's how the same thing looks as a Makefile:
Makefile # The variables INCLUDES contains the compiler flags that will add each student's # directory to the path the compiler uses to search for header files. INCLUDES = -I/home/m062562 -I/home/m053348 -I/home/m051038 -I/home/m056654 -I/home/m051284 # The variable STULIBS contains all the student libraries the program links to. # The backslash at the end of each line jus means "continue on next line as if # no line break were there". STULIBS = /home/m062562/libFreq.a \ /home/m053348/scaler_lib.a \ /home/m051038/librz.a \ /home/m056654/epslib.a \ /home/m051284/libpp.a # The variable GPHCSLIBS contains all the compiler flags to link to the X, OpenGL # and Glut libraries the program requires GPHCSLIBS = -L/usr/local/glut-3.7/lib/glut -lX11 -lXmu -lGL -lGLU -lglut ############### R U L E S ######################################################## all: T A B g++ -c cth.cpp ${INCLUDES} T A B g++ -o cth cth.o ${STULIBS} ${GPHCSLIBS}
Note: where I have "TAB" marked, you really need to
have a TAB there.
Now, to "run" this thing (assuming you save it in a file
named Makefile
) you simply type make
.
The make
utility looks for a file named
Makefile
in the current directory and, finding
it, executes what it finds therein. The section marked
RULES
contains ... well, rules. there can be
several rules, and wihtout any argument make executes the
first rule. If you type make all
, make
will execute the rule all
regardless of whether
or not there are rules before it in the Makefile.
The basic structure of make is that a target (right
now all
is the only target) can list other
targets as prerequisites. This is probably best shown with
the following example. It's not good make practice, but it
illustrates these ideas. All that I change from the above
Makefile is
the rules section:
What I do is break up compilation and linking into two separate targets. To complete target############### R U L E S ######################################################## all: compile T A B g++ -o cth cth.o ${STULIBS} ${GPHCSLIBS} compile: T A B g++ -c cth.cpp ${INCLUDES}
all
I must first complete target
compile
, and then I can take the action
associated with the all
target, which is to
link the program.
I make that happen by making a new target
compile
and listing it as a prerequisite of the
target all
. Make ensures that each
prerequisite of a target is met before taking the associated
actions. In this case, that means make will go to the
compile
target, perform its action (which is to
compile), and only then perform the linking command listed
as all
's action.
The next step in being a sophisticated make user is to write your makefiles so that make can look at the time stamps on files and make decisions on whether or not to recompile. Let's not worry about that for the moment!