Tip 69: Use a makefile for everything

17 February 12. [link] PDF version

level: shell user
purpose: organize your shell scripts

Part of a series of tips on POSIX and C. Start from the tip intro page, or get 21st Century C, the book based on this series.

A makefile is, for the most part, an organized set of shell scripts. Which is great, because you probably have a lot of silly little scripts floating around. Rather than producing a new one- or two-line script for every little task you have for your project, put them all into a makefile.

This site, for example, is generated via LATEX. Here's the sort of makefile one could write for the process. It's all lies: the actual process is much more involved, but the details wouldn't be relevant for you.

Oh, and if you cut and paste, don't forget that the white space at the head of each line must be a tab, not spaces. Blame POSIX.

all: doc publish

doc:
	pdflatex -interaction batchmode $(f).tex

publish:
	scp $(f).pdf $BLOGSERVER

I would use it like this:

f=119-tip-make_everything make
because that sets the environment variable f and then calls make with the variable set. This is why sh won't let you you put spaces around the equal sign: the space is how it distinguishes between the assignment and the command.

But having set f and run make, the first target, all, gets called, and that depends on doc and publish, which get called in sequence. If I know it's not yet ready to copy out to the world, then I can call make doc and just do that step.

I'm sure you could imagine lots of other things one would do with a document (word count, spell check, write to revision control, push revision control out to a remote, make backup) that involves a nontrivial line or three of code; all of that can go into the makefile so you don't have to think about it.

How makefiles and shell scripts differ

One more example target from my actual life. I use git here at home, but there are three subversion repositories I have to deal with. It's easy once you RTFM, but I never remember the sequence. Now I have a makefile to remember for me:

push:
	@if [ "x$(MSG)" = 'x' ] ; then echo "MSG='whatever, dude.'" make push; fi
	@test "x$(MSG)" != 'x'
	git commit -a  -m "$(MSG)"
	git svn fetch
	git svn rebase
	git svn dcommit

I need a message for each commit, so I do that via another environment variable set on the spot:

MSG="This is a commit." make

You can see that I have an if-then statement that prints a reminder if I forget this. Then, the makefile tests to make sure that "x$(MSG)" expands to something besides just 'x', meaning that MSG is not empty. This is a common shell idiom to make up for an idiotic POSIX glitch.

There you have it: makefiles aren't quite shell scripts for all these reasons, but you can summarize a lot of annoying procedural code in a makefile and never have to think about it again.


[Previous entry: "Tip 68: Write literate code"]
[Next entry: "Tip 70: Parse text with strtok"]