automated, scripts, shell

Learning to write (GNU) makefiles

I am a Mathematician and usually write more LaTeX than C but programming has been an active hobby since I owned my first Spectrum back in the eighties. What I am commenting in this post has been plaguing me for years and I have just learnt it (from laziness, no more): using pattern rules in makefiles. Caveat: I do not know whether this works for non-GNU Makes or not.

I had already read a lot of pro makefiles, which contained endless funny codes like $@, $%, $< etc. Today I have learnt the meaning of some of these.

I am supervising a project which needs compiling the same code with different versions of the same library. For the tests, we (this pupil of mine and I) had a makefile including the following jewel:

classic: *.c                                                         
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0a.c -o case0a_cl $(CL_LIB) 
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0b.c -o case0b_cl $(CL_LIB)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0c.c -o case0c_cl $(CL_LIB)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0d.c -o case0d_cl $(CL_LIB)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0e.c -o case0e_cl $(CL_LIB)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0f.c -o case0f_cl $(CL_LIB)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0g.c -o case0g_cl $(CL_LIB)

classic_no_fft: *.c
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0a.c -o case0a_cl_no_fft $(CL_LIB_NOFFT)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0b.c -o case0b_cl_no_fft $(CL_LIB_NOFFT)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0c.c -o case0c_cl_no_fft $(CL_LIB_NOFFT)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0d.c -o case0d_cl_no_fft $(CL_LIB_NOFFT)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0e.c -o case0e_cl_no_fft $(CL_LIB_NOFFT)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0f.c -o case0f_cl_no_fft $(CL_LIB_NOFFT)
        $(CC) -I$(CL_DIR) -L$(CL_DIR) case0g.c -o case0g_cl_no_fft $(CL_LIB_NOFFT)

(Yes, no way). That is what I was thinking while writing those lines; as a matter of fact, there are six other sections (profiling on/off, and a different version of the library with profiling as well on/off… you get the picture). But it worked, so why taking the trouble? Because one of our leading passions is learning. You can simplify all the stuff above with Make pattern rules. The simplest one (the one I used) is:

%_anything : %_whatever.c ; recipe

where _anything and _whatever are to be substituted by the needed text and recipe is the Make rule to build %_anything. In the above case, I just had to write:

%_cl : %.c ; $(CC) -I$(CL_DIR) -L$(CL_DIR) $< -o $@_cl $(CL_LIB)

%_th : %.c ; $(CC) -I$(CL_DIR) -L$(CL_DIR) $<.c -o $@_no_fft $(CL_LIB_NOFFT)

where there appear, in the recipe, the two funny codes $@ and $<. These are easy to understand: they are substituted inside the recipe by the part of the destination name corresponding to the % (this is the $@) pattern and the full name of the source (this is the $<) in each case for each of the targets. In the example, the targets are specified as

classic: case0a_cl case0b_cl case0c_cl case0d_cl case0e_cl case0f_cl case0g_cl

threads: case0a_cl case0b_th case0c_th case0d_th case0e_th case0f_th case0g_th

so that when one types

~$ make classic

The target case0a_cl, which falls into the rule %_cl : %.c, will be updated if case0a.c (the source corresponding to case0a_cl has no _cl in the name) has been modified since the last compilation. The recipe will be expanded to

 %_cl : %.c ; $(CC) -I$(CL_DIR) -L$(CL_DIR) case0a.c -o case0a_cl $(CL_LIB)

I am assuming the reader is familiar with Make’s variables (the $(CC) etc…).

So that I ended up with the following makefile (rather shorter, to be sure, than the original one):

CL_DIR=../../../mapm_4.9.5aa/
TH_DIR=../../
CC=gcc -g
CCP=gcc -g -pg
CL_LIB=-lm -lmapm
TH_LIB=-lm -lmapm -lpthread
CL_LIB_NOFFT=-lm -lmapm_no_fft -lpthread
TH_LIB_NOFFT=-lm -lmapm_no_fft -lpthread

%_cl : %.c ; $(CC) -I$(CL_DIR) -L$(CL_DIR) $< -o $@ $(CL_LIB)

%_th : %.c ; $(CC) -I$(TH_DIR) -L$(TH_DIR) $< -o $@ $(TH_LIB)

%_cl_no_fft : %.c ; $(CC) -I$(CL_DIR) -L$(CL_DIR) $< -o $@ $(CL_LIB_NOFFT)

%_th_no_fft: %.c ; $(CC) -I$(TH_DIR) -L$(TH_DIR) $< -o $@ $(TH_LIB_NOFFT)

default: all

classic: case0a_cl case0b_cl case0c_cl case0d_cl case0e_cl case0f_cl case0g_cl

classic_no_fft: case0a_cl_no_fft case0b_cl_no_fft case0c_cl_no_fft \
		case0d_cl_no_fft case0e_cl_no_fft case0f_cl_no_fft case0g_cl_no_fft


threads: case0a_th case0b_th case0c_th case0d_th case0e_th case0f_th case0g_th

threads_no_fft: case0a_th_no_fft case0b_th_no_fft case0c_th_no_fft \
		case0d_th_no_fft case0e_th_no_fft case0f_th_no_fft case0g_th_no_fft

all: classic threads classic_no_fft threads_no_fft

test: all
	./run_all.sh

clean:
	rm -f *~ *th *cl *_no_fft

I reckon all those rather awful lists might be shortened somehow, but it will probably take me a couple of years to learn.

speak up

Add your comment below, or trackback from your own site.

Subscribe to these comments.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*Required Fields