Fun with Make

I usually don’t have a reason to muck around with GNU make but I find myself sitting on a train from Ronkonkoma to Jamaica (LI to Queens, in New York) with not much to do (and no internet access), so I figured I’d do some scribbling. (BTW, I’m using the shell in Mac OS X for this, but all of these commands should work fine on Linux, for using make on Windows, check out sourceforge.net for options)

The man page provides some good info but what is really helpful is the info documentation. To access it, type:

info make

Info is usually too verbose for what people are looking for when working with the shell, but since make is more than just a simple shell command it is definitely worth a read.

So, what is make?

From the man page:

“The purpose of the make utility is to determine automatically which pieces of a large program need to be recompiled, and issue the commands to recompile them.”

So, think of it as a management system for your program. As mentioned in the documentation, make is not limited to compiled programs, or even programs at all. If you have some type of file that needs a command (or set of commands) executed after it is modified you can use make for the job!

An example.

So, lets create a small C program. We’ll call it junk.c:

#include

int main()
{

printf("Hello Worldn");
return 0;

}

Simple enough, right? Good. Now, I would normally compile this program with:

cc junk.c -o junk

and once you do that, you’ll see that you have an executable called ‘junk’ which you can run with

./junk

(if not, check permissions and see if you actually have an executable file called ‘junk’)

Fine. So, lets throw make into the mix. We’ll create a file called “Makefile” (notice the capital ‘M’, the capital ‘M’ is not required, but is convention and convenient as it separates this file from your source files in the same directory) in the same directory. That file looks like this:

# Makefile
# build junk executable

junk : junk.o

junk.o : junk.c
cc junk.c

so, once you this file created, lets run the make command:

make

You should receive output similar to this:
~/junk supertom$ make
cc junk.o -o junk
~/junk supertom$

If you run make again, you should receive output similar to this:

~/junk supertom$ make
make: `junk' is up to date.
~/junk supertom$

That’s the whole idea behind the make command. It acts as a manager for your program files. If they need to be recompiled they will be, otherwise it will do nothing. May not be a big deal with our simple program but if you are dealing with lots of program files compiling only the modified files is a big time saver. To force make to recompile in this case just remove the junk executable.

Ok, back to our example. Looking at our Makefile, “junk” is the ultimate name of our executable and to build it, we need junk.o. In terms of make, both junk and junk.o are called targets. You can think of targets as appearing to the left of the ‘:’ in the file. So, the “junk” target needs file “junk.o” and “junk.o” needs junk.c. Notice the command:

cc junk.c

This is command to be run when that target is encountered. Commands that you want executed by make must be prefaced by a tab. The make utility is actually pretty smart when it comes to C files, so if you look at the makefiles for current applications it might not be so verbose. Don’t forget, however, that make can be used for other things, so take not of what is going on in our example.

Ok, lets pick it up a bit:

# Makefile
# builds the junk executable

prg = junk
objects = junk.o

$(prg) : $(objects)

# what would normally explicitly be here...
#junk.o : junk.c

# make knows what to do with .c files so you can actually leave it out
# it knows that .c -> .o
# junk.o :

# and, we can put a var in, like this:
$(objects) :

# .IGNORE gets us around the problem of rm not finding the files to delete
.IGNORE clean :
rm $(prg) *~ *.o

tarup :
tar -czf $(prg).tar.gz $(prg).c

So, what are the targets in this file? junk, clean and tarup. “junk” is our first target and is the one that will be executed if no other targets are specified. “clean” runs the rm command to remove backup and .o files and tarup executes a command to create a tarball of junk.c. So, I can execute these targets with:

make clean
make (or make junk)
make tarup

We’ve introduced some new concepts in our Makefile, so lets discuss briefly. One is the concept of variables. See prg and objects at the top? Yup, those are vars. Similar to the bash shell, we declare and initialize with no distinct chars, but then use it with $ and parentheses. You’ll also see that we’re a little less verbose about what is going on. As I mentioned earlier, make is smart about C files and knows what to do with them.

We’ve also introduced the “phony” target .IGNORE which keeps us from receiving errors from the rm command. Ever try to rm something that didn’t exist? You’ll receive an error. Maybe not such a big deal to you now, but if you try to include it in a dependent sequence of commands, like

make clean && make

make will never execute because make clean will return with a status of 1 if there are no ~* or *.o files.

So, my train ride is about over, but here are some thoughts/reasons/ideas on how/when to use make for non-compiled applications:

  • Creating a “package” out of your application – especially in web programming, we tend to think of these files as separate, but they are heavily dependant on each other. You could use make to create a tarball of your app
  • Things that need to happen before or after a “build” – update a build file with date/time information, suck in a sql file that needs to be included in the package, tag a cvs/svn branch or include some other metadata