Running Linux, 5th Edition - Matthias Kalle Dalheimer [388]
Macros
When people use a filename or other string more than once in a makefile, they tend to assign it to a macro. That's simply a string that make expands to another string. For instance, you could change the beginning of our trivial makefile to read as follows:
OBJECTS = main.o edit.o
edimh: $(OBJECTS)
gcc -o edimh $(OBJECTS)
When make runs, it simply plugs in main.o edit.o wherever you specify $(OBJECTS). If you have to add another object file to the project, just specify it on the first line of the file. The dependency line and command will then be updated correspondingly.
Don't forget the parentheses when you refer to $(OBJECTS). Macros may resemble shell variables like $HOME and $PATH, but they're not the same.
One macro can be defined in terms of another macro, so you could say something like:
ROOT = /usr/local
HEADERS = $(ROOT)/include
SOURCES = $(ROOT)/src
In this case, HEADERS evaluates to the directory /usr/local/include and SOURCES to /usr/local/src. If you are installing this package on your system and don't want it to be in /usr/local, just choose another name and change the line that defines ROOT.
By the way, you don't have to use uppercase names for macros, but that's a universal convention.
An extension in GNU make allows you to add to the definition of a macro. This uses a := string in place of an equals sign:
DRIVERS = drivers/block/block.a
ifdef CONFIG_SCSI
DRIVERS := $(DRIVERS) drivers/scsi/scsi.a
endif
The first line is a normal macro definition, setting the DRIVERS macro to the filename drivers/block/block.a. The next definition adds the filename drivers/scsi/scsi.a. But it takes effect only if the macro CONFIG_SCSI is defined. The full definition in that case becomes:
drivers/block/block.a drivers/scsi/scsi.a
So how do you define CONFIG_SCSI? You could put it in the makefile, assigning any string you want:
CONFIG_SCSI = yes
But you'll probably find it easier to define it on the make command line. Here's how to do it:
papaya$ make CONFIG_SCSI=yestarget_name
One subtlety of using macros is that you can leave them undefined. If no one defines them, a null string is substituted (that is, you end up with nothing where the macro is supposed to be). But this also gives you the option of defining the macro as an environment variable. For instance, if you don't define CONFIG_SCSI in the makefile, you could put this in your .bashrc file, for use with the bash shell:
export CONFIG_SCSI=yes
Or put this in .cshrc if you use csh or tcsh:
setenv CONFIG_SCSI yes
All your builds will then have CONFIG_SCSI defined.
Suffix Rules and Pattern Rules
For something as routine as building an object file from a source file, you don't want to specify every single dependency in your makefile. And you don't have to. Unix compilers enforce a simple standard (compile a file ending in the suffix .c to create a file ending in the suffix .o), and make provides a feature called suffix rules to cover all such files.
Here's a simple suffix rule to compile a C source file, which you could put in your makefile:
.c.o:
gcc -c $(CFLAGS) $<
The .c.o: line means "use a .c dependency to build a .o file." CFLAGS is a macro into which you can plug any compiler options you want: -g for debugging, for instance, or -O for optimization. The string $< is a cryptic way of saying "the dependency." So the name of your .c file is plugged in when make executes this command.
Here's a sample run using this suffix rule. The command line passes both the -g option and the -O option:
papaya$ make CFLAGS="-O -g" edit.o
gcc -c -O -g edit.c
You actually don't have to specify this suffix rule in your makefile because something very similar is already built into make. It even uses CFLAGS, so you can determine the options used for compiling just by setting that variable. The makefile used to build the Linux kernel currently contains the following definition, a whole slew of gcc options:
CFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -pipe
While we're discussing compiler