Over the years, the community has moved from the stance of writing portable Makefiles to a stance of just using a powerful, portable make. The make bible from O'Reilly has come out in a new (third) edition, with the new title of Managing projects with GNU Make and a new author, Robert Mecklenburg, 2005, ISBN 0-596-00610-1. If you have been considering learning more about make, and perhaps dusting off your Makefiles, read this book.Make has a portable subset of features, with system-dependent extensions. If you want to use extensions, I suggest sticking with those supported by gnu make (gmake), since it is available most everywhere.
Make Rules
The core of make hasn't changed in decades, but concentrating on gmake allows one to make use of its nifty little extras designed by real programmers to help with real projects. The first change that matters to my Makefiles is change from suffix rules to pattern rules. I've always found the .SUFFIXES list to be odd, especially since .f90 is not in the default list. Good riddance to all of that! For a concrete example, the old way to provide a rule for going from file.f90 to file.o is:
.SUFFIXES: .o .f90 .F .F90
.f90.o:
<TAB> $(FC) -c $(FFLAGS) $<
while the new way is:
%.o: %.f90
<TAB> $(FC) -c $(FFLAGS) $<
In fact, going to a uniform make means that we can figure out what symbols are defined and use their standard values - in this case, $(FC) and $(FFLAGS) are the built-in default names. If you have any questions about this, you can always run make with the -p (or --print-data-base) option. This prints out all the rules make knows about, such as:
# default
COMPILE.f = $(FC) $(FFLAGS) $(TARGET_ARCH) -c
Printing the rules database will show variables that make is picking up from the environment, from the Makefile, and from its built-in rules - and which of these sources is providing each value.
Assignments
In the old days, I only used one kind of assignment: = (equals sign). Gmake has several kinds of assignment (other makes might as well, but I no longer have to know or care). An example of the power of gnu make is shown by an example from my Cray X1 Makefile. There is a routine which runs much more quickly if a short function in another file is inlined. The way to accomplish this is through the -O inlinefrom=file directive to the compiler. I can't add this option to FFLAGS, since the inlined routine won't compile with this directive - it is only the one file that needs it. I had:
FFLAGS = -O 3,aggress -e I -e m
FFLAGS2 = -O 3,aggress -O inlinefrom=lmd_wscale.f90 -e I -e m
lmd_skpp.o:
<TAB> $(FC) -c $(FFLAGS2) $*.f90
The first change I can make to this using other assignments is:
FFLAGS := -O 3,aggress -e I -e m
FFLAGS2 := $(FFLAGS) -O inlinefrom=lmd_wscale.f90
The := assignment means to evaluate the right hand side immediately. In this case, there is no reason not to, as long as the second assigment follows the first one (since it's using the value of $(FFLAGS)). For the plain equals, make doesn't evaluate the right-hand side until its second pass through the Makefile. However, gmake supports an assignment that avoids the need for FFLAGS2 entirely:
lmd_skpp.o: FFLAGS += -O inlinefrom=lmd_wscale.f90
What this means is that for the target lmd_skpp.o only, append the inlining directive to FFLAGS. I think this is pretty cool!
One last kind of assignment is to set the value only if there is no value from somewhere else (the environment, for instance):
FC ?= mpxlf90_r
If we use := or =, we would override the value from the environment.
Portability
Using gmake across all platforms solves some portability issues, but not all. For instance, there is an example in chapter one which uses the pr command with some gnu-only options. The IBM has an older pr in /usr/bin and the gnu version in /opt/freeware/bin. The example failed until I changed my path to make the gnu version my default.
The book has a chapter on portability with an emphasis on cygwin, which is great if you use cygwin. There is also an example of extracting the machine-dependent parts of the Makefile into a series of include files:
MACHINE := $(shell uname -sm | send 's/ /-/g')
include $(MACHINE)-defines.mk
running uname -sm on the IBM gives differing values for the -m option between iceberg and iceflyer - perhaps uname -s is enough for my needs. Having an automatic way to set the MACHINE type is nice, but our users are using more than one Fortran compiler under Linux, so knowing we're on Linux is only half the battle.
As I've described before, I have been using imake to build Makefiles for all the different systems I have access to. Robert Mecklenburg claims that gmake is powerful enough to eliminate the need for imake. I'll let you know if I agree as I read more of this book and continue to update my Makefiles.