Chapter 15 Writing Makefiles

Table of Contents
15.1 Basics
15.2 Defining Targets
15.3 Defining Target Properties
15.4 Defining Custom Rules
15.5 Uses
15.6 Linksets
15.7 Portability Considerations
15.8 How Does it Work?

15.1 Basics

Writing good, portable makefiles is not trivial. Once you have written one such makefile, you would only modify a tiny part of it for different projects. This is what the release tools makefile body does for your makefiles: it factors out the common parts, leaving your files quite abstract and hopefully very understandable. While the makefile body goes a long way to hide platform and compiler details and to be as flexible towards developers' desires as possible, it cannot hide all those details from you. You should be aware that the task of writing makefiles is made harder by varying demands from other developers. In short, people want to customise packages: what works for you may not be to their liking. Adding all the right hooks and testing them is no small undertaking. The release tools try to do their best to provide for all this, but at times they need your co-operation.

The justification for almost all of this hassle can be expressed in one word: portability. Not only does this mean that it should be possible to compile your package with compilers or on operating systems you have not tried out. Of course this has limits--one cannot expect to use a Windows NT-specific GUI application to compile on unixen--but you can still do much to enable this adaption in reasonable limits. Basically, it should be possible to move the package to any machine, any operating system variant or version, any user account, and any directory--and it should still be within limits of reason to expect it to work. The package should simply adapt itself to the environment in which it is used, provided that it is possible with sensible effort. You can get a lot of leverage by using the release tools makefile body and GNU autoconf--both were conceived for different parts of that task.

We will assume for this chapter that you have basic knowledge of what GNU make does and how it works.[1] If you do not, this is not going to be a good place to start learning; rather, you will probably be more confused than helped by this text. You can learn about GNU make by studying its manual--it is good reading for beginners but beneficial also to advanced users. Another good reference you might want to have within reach is the O'Reilly book on GNU make.

Let us start with two examples of typical package makefiles: first a simple one and then a little more complex one. You should be able to understand what they do at a high level at the first glance, especially for the first of them. As you are reading the makefiles, you may notice a common layout and structure in them.

Example 15-1. Example of A Simple Makefile

# $Id-*- makefile -*-
# $Name
##################################################################
TARGET_LIBS             = libclass.a

libclass.a_USES         = c++
libclass.a_SRC          = debug.cc errors.cc file.cc     \
                          io.cc log.cc sequence.cc       \
                          socket.cc time.cc utilities.cc \
                          repo.cc

##################################################################
include $(SRT_HOME)/standard.mk

Example 15-2. Example of A More Complex Makefile

# $Id-*- makefile -*-
# $Name
#
# Makefile for ANTLR 1.33.  Adapted from the distribution makefile
##################################################################

TARGET_BINS             = antlr

ANTLR                   = ./antlr
DLG                     = ./dlg

antlr_USES              = c
antlr_OPTION_SETS       = optimise
antlr_CPPFLAGS          = -I. -I$(top_srcdir)/support/set \
                          -I$(top_srcdir)/h -DUSER_ZZSYN
antlr_SRC               = antlr.c scan.c err.c bits.c build.c   \
                          fset2.c fset.c gen.c globals.c hash.c \
                          lex.c main.c misc.c set.c pred.c

##################################################################
include $(SRT_HOME)/standard.mk

set.c: $(top_srcdir)/support/set/set.c
        cp $< $@

You may have recognised the common structure of these makefiles. First they use the TARGET variables to define what is to be built. Then they give detailed information about each target by defining property variables. These are followed by the inclusion of the standard.mk and possibly by some custom rules. This is, in fact, the basic structure of any makefile that uses the release tools' definitions. Basically the package author defines what is to be done and the standard makefile body interprets these definitions.

Even when the makefiles extensively use the help of the release tools, they still remain normal makefiles. This means that when the facilities provided by the releases tools come short, you can fill the gaps simply by writing the bits that are missing. It may not be as abstract and clean as what the release tools provide you, but it gets the job done.

Notes

[1]

The release tools standardise on using the GNU version; no other make will do. Part of this deal is that you can use the GNU extensions, there is no reason to go out of your way to try to make things compatible with another make.

[1]

Do not worry about the particular names here; this example comes directly from a certain package.

[2]

Or dynamic shared objects, or dunamic link libraries, or whatever the operating system happens to call them.

[3]

Some tar programs are broken and fail to copy directories properly. In particular, the native tar on HP-UX is known to cause trouble. Just make sure you have a working tar (such as GNU tar) in your path.

[4]

At the moment this variable will not work properly in package subdirectories: make will not understand to update the makefile properly. This is because the target must be mentioned relative to the package root such as test/GNUmakefile. On the other hand, in the test subdirectory the target should be called GNUmakefile for make to realise that it should apply the rule.

[1]

Here is the long story. When the object files are closed with respect to templates, many C++ compilers instantiate templates not to separate object files, but to the object files going into the library. Thus, the templates used by the library will be defined in that library and clients can successfully link. However, the problem arises when the templates are really external to your library--for example if you are using the standard library class vector--as some other library may instantiate the template as well. The loop is completed by the behaviour of many linkers: when they resolve a symbol, they take the entire object file that defines the symbol, not just the symbol itself. Now, if the linker chooses two object files from two different libraries such that both of them instantiate the same templates, a linker error will occur due to duplicate symbols. You can avoid this to a certain extent by carefully constructing the lists of libraries used in prelinking. This will not, however, be a complete solution: it will only push the problematic cases further into the future. The issue only goes away when the compiler properly implements templates and external compilation (the export keyword). Neither will this practise remove the problem that you can get massive amounts of libraries brought in just because a linker picked up template symbols from them. For example, you can have a completely unrelated library brought in in its entirety just because the linker resolved vector<int> from it.

[1]

Only the last name part, not the leading directories.

[2]

If you do not dynamically configure your linksets, you will need to name the libraries from the same makefile. Otherwise the release tools will not know the ordering dependencies. However, if you use linkset -frag option and tell the release tools how the linksets in your package depend on each other via the needs variables, you do not need to define the LIBS property. You will still need to define the dependencies though--otherwise make might try to build targets in incorrect order.

[1]

Except for the rules that are meant to be used solely by the package authors. For such rules it is safe to assume that the developers know what restrictions apply.

[2]

``Almost never'' because under a certain circumstances one would want to write in there. These are always situations where the package author is in control. An example would be updating configure.

[1]

Yes, you read that right: makefile uses rarely define any rules themselves. Usually they only create rules for a target. This is one reason why you must state all the uses the target needs: otherwise the appropriate rules will not be created into the makefile fragment and make will not know what to do.