Class 11: Makefiles


Reading
MIDN Jacobbi found this link about Makefiles, and he posted this example for you.

Section 15 of the Art of Unix Programming is also all about Make. Read it!

Homework
From the below discussion on Makefiles, you should see enough to allow you to write a simple makefile for your program/library. Your makefile should have, at the very least, a target that creates your program, and a target that creates your libarary. Suppose the target associated with the libary was called 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.


Finishing up dynamic/shared object libraries
Our main activity for last class was to get everyone's dynamic/shared object libraries up, in the right places with the right permissions, and actually tied together in a working program. Whew! Not that easy! At the end of class, with the bell still echoing through the room, we had Mr. Jacobbi modify a function in his shared object library, and watched as I ran a program that relyed on the library, but which had not been touched in any way, and saw the program reflect Mr. Jacobbi's changes. That's what dynamic/shared object libraries are all about.

Motivation for Make
The 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 -lglut 
What 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:

############### 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}
What I do is break up compilation and linking into two separate targets. To complete target 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!


Christopher W Brown
Last modified: Thu Feb 3 16:00:00 EST 2005