Programming Utilities Guide

Using make to Maintain Libraries and Programs

In previous sections you learned how make can help compile simple programs and build simple libraries. This section describes some of make's more advanced features for maintaining complex programs and libraries.

More about Macros

Macro definitions can appear on any line in a makefile; they can be used to abbreviate long target lists or expressions, or as shorthand to replace long strings that would otherwise have to be repeated.

You can even use macros to derive lists of object files from a list of source files. Macro names are allocated as the makefile is read in; the value a particular macro reference takes depends upon the most recent value assigned.


Note -

Macro evaluation is more complicated than described here. Refer to ...


With the exception of conditional and dynamic macros, make assigns values in the order the definitions appear.

Embedded Macro References

Macro references can be embedded within other references (not supported in previous versions of make)..

$(CPPFLAGS$(TARGET_ARCH))

Note -

The += assignment appends the indicated string to any previous value for the macro.


In which case they are expanded from innermost to outermost. With the following definitions, make will supply the correct symbol definition for (for example) a Sun-4 system.

CPPFLAGS-sun4 = -DSUN4 
CPPFLAGS += $(CPPFLAGS-$(TARGET_ARCH))

Suffix Replacement in Macro References

make provides a mechanism for replacing suffixes of words that occur in the value of the referred-to macro. Although conventional suffixes start with dots, a suffix can consist of any string of characters. A reference of the form:

$(macro:old-suffix=new-suffix)

is a suffix replacement macro reference. You can use such a reference to express the list of object files in terms of the list of sources:

OBJECTS= $(SOURCES:.c=.o)

In this case, make replaces all occurrences of the .c suffix in words within the value with the .o suffix. The substitution is not applied to words that do not end in the suffix given. The following makefile:

SOURCES= main.c data.c moon 
OBJECTS= $(SOURCES:.c=.o) 

all: 
         @echo $(OBJECTS)

offers a simple illustration:

$ make 
main.o data.o moon

Using lint with make

For easier debugging and maintenance of your C programs use the lint tool. lint also checks for C constructs that are not considered portable across machine architectures. It can be a real help in writing portable C programs.

lint, the C program verifier, is an important tool for forestalling the kinds of bugs that are most difficult and tedious to track down. These include uninitialized pointers, parameter-count mismatches in function calls, and non-portable uses of C constructs. As with the clean target, lint is a target name used by convention; it is usually a good practice to include it in makefiles that build C programs. lint produces output files that have been preprocessed through cpp and its own first (parsing) pass. These files characteristically end in the .ln suffix and can also be derived from the list of sources through suffix replacement (this might not be true for older versions of lint):

LINTFILES= $(SOURCES:.c=.ln)

A target entry for the lint target might appear as:

lint: $(LINTFILES) 
         $(LINT.c) $(LINTFILES) 
$(LINTFILES): 
        	$(LINT.c) $@ -i

There is an implicit rule for building each .ln file from its corresponding .c file, so there is no need for target entries for the .ln files. As sources change, the .ln files are updated whenever you run

make lint

Since the LINT.c predefined macro includes a reference to the LINTFLAGS macro, it is a good idea to specify the lint options to use by default (none in this case). Since lint entails the use of cpp, it is a good idea to use CPPFLAGS, rather than CFLAGS for compilation preprocessing options (such as -I). The LINT.c macro does not include a reference to CFLAGS.

Also, when you run make clean, you will want to get rid of any .ln files produced by this target. It is not difficult to add another such macro reference to a clean target.

Linking with System-Supplied Libraries

The next example shows a makefile that compiles a program that uses the curses and termlib library packages for screen-oriented cursor motion.

Table 4-10 Makefile for a C Program with System-Supplied Libraries
# Makefile for a C program with curses
# and termlib.  

CFLAGS= -O 

.KEEP_STATE:

functions: main.o data.o 
        	$(LINK.c) -o $@ main.o data.o -lcurses
         -ltermlib 
lint: main.ln data.ln 
        	$(LINT.c) main.ln data.ln 
main.ln data.ln: 
        	$(LINT.c) $@ -i 
clean: 
        	rm -f functions main.o data.o main.ln \
         data.ln

Because the link editor resolves undefined symbols as they are encountered, it is normally a good idea to place library references at the end of the list of files to link.

This makefile produces:

$ make 
cc -O -c main.c 
cc -O -c data.c 
cc -O -o functions main.o data.o -lcurses -ltermlib

Compiling Programs for Debugging and Profiling

Compiling programs for debugging or profiling introduces a new twist to the procedure and to the makefile. These variants are produced from the same source code, but are built with different options to the C compiler. Use the cc -g option to produce object code that is suitable for debugging. The cc options that produce code for profiling are -O and -pg.

Because the compilation procedure is the same otherwise, you could give make a definition for CFLAGS on the command line. Since this definition overrides the definition in the makefile, and .KEEP_STATE assures any command lines affected by the change are performed, the following make command produces the results as presented in this example:

$ make "CFLAGS= -O -pg" 
cc -O -pg -c main.c 
cc -O -pg -c data.c 
cc -O -pg -o functions main.o data.o -lcurses -ltermlib

Of course, you might not want to memorize these options or type a complicated command like this, especially when you can put this information in the makefile. What is needed is a way to tell make how to produce a debugging or profiling variant, and some instructions in the makefile that tell it how. One way to do this might be to add two new target entries, one named debug, and the other named profile, with the proper compiler options hard-coded into the command line.

A better way would be to add these targets, but rather than hard-coding their rules, include instructions to alter the definition of CFLAGS, depending upon which target it starts with. Then, by requiring each one to depend on the existing target for functions, make could use its rule, along with the specified options.

Instead of saying:

make "CFLAGS= -g"

to compile a variant for debugging, you could say:

make debug

The question is, how do you tell make that you want a macro defined one way for one target (and its dependencies), and another way for a different target?

Conditional Macro Definitions

A conditional macro definition is a line of the form:

target-list := macro = value

which assigns the given value to the indicated macro while make is processing the target named target-list and its dependencies.


Note -

Each word in target-list can contain one % pattern; make must know which targets the definition applies to, so you cannot use a conditional macro definition to alter a target name.


The following lines give CFLAGS an appropriate value for processing each program variant.

debug := CFLAGS= -g 
profile := CFLAGS= -pg -O

Notice that when you use a reference to a conditional macro in the dependency list, that reference must be delayed (by prepending a second $). Otherwise, make expands the reference before the correct value has been assigned. When it encounters a (possibly) incorrect reference of this sort, make issues a warning.

Compiling Debugging and Profiling Variants

The following makefile produces optimized, debugging, or profiling variants of a C program, depending on which target you specify (the default is the optimized variant). Command dependency checking guarantees that the program and its object files will be recompiled whenever you switch between variants.

Table 4-11 Makefile for a C Program with Alternate Debugging and Profiling Variants
# Makefile for a C program with alternate 
# debugging and profiling variants.  

CFLAGS= -O 

.KEEP_STATE:

all debug profile: functions 

debug := CFLAGS = -g 
profile := CFLAGS = -pg -O 

functions: main.o data.o 
       	$(LINK.c) -o $@ main.o data.o -lcurses
        -ltermlib 
lint: main.ln data.ln 
       	$(LINT.c) main.ln data.ln 
clean: 
       	rm -f functions main.o data.o main.ln data.ln

The first target entry specifies three targets, starting with all.


Note -

Debugging and profiling variants are not normally considered part of a finished program.


all traditionally appears as the first target in makefiles with alternate starting targets (or those that process a list of targets). Its dependencies are "all" targets that go into the final build, whatever that might be. In this case, the final variant is optimized. The target entry also indicates that debug and profile depend on functions (the value of $(PROGRAM)).

The next two lines contain conditional macro definitions for CFLAGS.

Next comes the target entry for functions. When functions is a dependency for debug, it is compiled with the -g option.

The next example applies a similar technique to maintaining a C object library.

Table 4-12 Makefile for a C Library with Alternate Variants
# Makefile for a C library with alternate 
# variants.  

CFLAGS= -O 

.KEEP_STATE
.PRECIOUS:  libpkg.a

all debug profile: libpkg.a 
debug := CFLAGS= -g 
profile := CFLAGS= -pg -O 

libpkg.a: libpkg.a(calc.o map.o draw.o) 
         	ar rv $@ $? 
         	libpkg.a(%.o): %.o 
         	@true 
lint: calc.ln map.ln draw.ln 
         	$(LINT.c) calc.ln map.ln draw.ln 
clean: 
         	rm -f libpkg.a calc.o map.o draw.o \
          calc.ln map.ln draw.ln

Maintaining Separate Program and Library Variants

The previous two examples are adequate when development, debugging, and profiling are performed in distinct phases. However, they suffer from the drawback that all object files are recompiled whenever you switch between variants, which can result in unnecessary delays. The next two examples illustrate how all three variants can be maintained as separate entities.

To avoid the confusion that might result from having three variants of each object file in the same directory, you can place the debugging and profiling object files and executables in subdirectories. However, this requires a technique for adding the name of the subdirectory as a prefix to each entry in the list of object files.

Pattern-Replacement Macro References

A pattern-replacement macro reference is similar in form and function to a suffix replacement reference. You can use a pattern-replacement reference to add or alter a prefix, suffix, or both, to matching words in the value of a macro.


Note -

As with pattern-matching rules, pattern-replacement macro references are not available in earlier versions of make.


A pattern-replacement reference takes the form:

$(macro:p%s =np%ns)

where p is the existing prefix to replace (if any), s is the existing suffix to replace (if any), np and ns are the new prefix and new suffix, and % is a wild card. The pattern replacement is applied to all words in the value that match `p%s. For instance:

SOURCES= old_main.c old_data.c moon 
OBJECTS= $(SOURCES:old_%.c=new_%.o) 
all: 
        	@echo $(OBJECTS)

produces:

$ make 
new_main.o new_data.o moon

You can use any number of % wild cards in the right-hand (replacement) side of the = sign, as needed. The following replacement:

...
OBJECTS= $(SOURCES:old_%.c=%/%.o)

would produce:

main/main.o data/data.o moon

Note, however, that pattern-replacement macro references should not appear in the dependency line of the target entry for a pattern-matching rule. This produces a conflict, since make cannot tell whether the wild card applies to the macro, or to the target (or dependency) itself. With the makefile:

OBJECT= .o 

x: 
x.Z: 
        	@echo correct 
%: %$(OBJECT:%o=%Z)

it seems as if make should attempt to build x from x.Z. However, the pattern-matching rule is not recognized; make cannot determine which of the % characters in the dependency line to use in the pattern-matching rule.

Makefile for a Program with Separate Variants

The following example shows a makefile for a C program with separately maintained variants. First, the .INIT special target creates the debug_dir and profile_dir subdirectories (if they do not already exist), which will contain the debugging and profiling object files and executables.


Note -

make performs the rule in the .INIT target just after the makefile is read.


The variant executables are made to depend on the object files listed in the VARIANTS.o macro. This macro is given the value of OBJECTS by default; later on it is reassigned using a conditional macro definition, at which time either the debug_dir/ or profile_dir/ prefix is added. Executables in the subdirectories depend on the object files that are built in those same subdirectories.

Next, pattern-matching rules are added to indicate that the object files in both subdirectories depend upon source (.c) files in the working directory. This is the key step needed to allow all three variants to be built and maintained from a single set of source files.

Finally, the clean target has been updated to recursively remove the debug_dir and profile_dir subdirectories and their contents, which should be regarded as temporary. This is in keeping with the custom that derived files are to be built in the same directory as their sources, since the subdirectories for the variants are considered temporary.

Table 4-13 Sample Makefile for Separate Debugging and Profiling Program Variants
# Simple makefile for maintaining separate
# debugging and profiling program variants.  

CFLAGS= -O 

SOURCES= main.c rest.c 
OBJECTS= $(SOURCES:%.c=$(VARIANT)/%.o) 
VARIANT= .

functions profile debug: $$(OBJECTS) 
 $(LINK.c) -o $(VARIANT)/$@ $(OBJECTS) 

debug := VARIANT = debug_dir
debug := CFLAGS = -g 
profile := VARIANT = profile_dir
profile := CFLAGS = -O -pg 

.KEEP_STATE:
.INIT:  profile_dir debug_dir
profile_dir debug_dir:
 test -d $@ || mkdir $@ 
 $$(VARIANT)/%.o: %.c 
 $(COMPILE.c) $< -o $@ 
clean:  
 rm -r profile_dir debug_dir $(OBJECTS) functions

Makefile for a Library with Separate Variants

The modifications for separate library variants are quite similar:

# Makefile for maintaining separate library variants.  

CFLAGS= -O 

SOURCES= main.c rest.c 
LIBRARY= lib.a 
LSOURCES= fnc.c 

OBJECTS= $(SOURCES:%.c=$(VARIANT)/%.o) 
VLIBRARY= $(LIBRARY:%.a=$(VARIANT)/%.a) 
LOBJECTS= $(LSOURCES:%.c=$(VARIANT)/%.o) 
VARIANT= .

program profile debug: $$(OBJECTS) $$(VLIBRARY) 
         	$(LINK.c) -o $(VARIANT)/$@ $< 

lib.a debug_dir/lib.a profile_dir/lib.a: $$(LOBJECTS) 
         	ar rv $@ $? 

$$(VLIBRARY)($$(VARIANT)%.o): $$(VARIANT)%.o 
        	@true 
profile := VARIANT = profile_dir
profile := CFLAGS = -O -pg 

debug := VARIANT = debug_dir
debug := CFLAGS = -g 

.KEEP_STATE:
profile_dir debug_dir:
        	test -d $@ || mkdir $@ 
$$(VARIANT)/%.o: %.c 
        	$(COMPILE.c) $< -o $@

While an interesting and useful compilation technique, this method for maintaining separate variants is a bit complicated. For the sake of clarity, it is omitted from subsequent examples.

Maintaining a Directory of Header Files

The makefile for maintaining an include directory of headers is quite simple. Since headers consist of plain text, all that is needed is a target, all, that lists them as dependencies. Automatic SCCS retrieval takes care of the rest. If you use a macro for the list of headers, this same list can be used in other target entries.

# Makefile for maintaining an include directory.  

FILES.h= calc.h map.h draw.h 

all: $(FILES.h) 

clean: 
        	rm -f $(FILES.h)

Compiling and Linking with Your Own Libraries

When preparing your own library packages, it makes sense to treat each library as an entity that is separate from its header(s) and the programs that use it. Separating programs, libraries, and headers into distinct directories often makes it easier to prepare makefiles for each type of module. Also, it clarifies the structure of a software project.


Note -

It is not a good idea to have things pop up all over the file system as a result of running make.


A courteous and necessary convention of makefiles is that they only build files in the working directory, or in temporary subdirectories. Unless you are using make specifically to install files into a specific directory on an agreed-upon file system, it is regarded as very poor form for a makefile to produce output in another directory.

Building programs that rely on libraries in other directories adds several new wrinkles to the makefile. Up until now, everything needed has been in the directory, or else in one of the standard directories that are presumed to be stable. This is not true for user-supplied libraries that are part of a project under development.

Because these libraries aren't built automatically (there is no equivalent to hidden dependency checking for them), you must supply target entries for them. On the one hand, you need to ensure the libraries you link with are up to date.

On the other, you need to observe the convention that a makefile should only maintain files in the local directory. In addition, the makefile should not contain information duplicated in another.

Nested make Commands

The solution is to use a nested make command, running in the directory the library resides in, to rebuild it (according to the target entry in the makefile there)


Note -

The MAKE macro, which is set to the value ``make'' by default, overrides the -n option. Any command line in which it is referred to is executed, even though -n might be in effect. Since this macro is used to invoke make, and since the make it invokes inherits -n option from the special MAKEFLAGS macro, make can trace a hierarchy of nested make commands with the -n option.


# First cut entry for target in another directory.  

../lib/libpkg.a:
        	cd ../lib ; $(MAKE) libpkg.a

The library is specified with a path name relative to the current directory. In general, it is better to use relative path names. If the project is moved to a new root directory or machine, so long as its structure remains the same relative to that new root directory, all the target entries will still point to the proper files.

Within the nested make command line, the dynamic macro modifiers F and D come in handy, as does the MAKE predefined macro. If the target being processed is in the form of a pathname, $(@F) indicates the filename part, while $(@D) indicates the directory part. If there are no / characters in the target name, then $(@D) is assigned the dot character (.) as its value.

The target entry can be rewritten as:

# Second cut.  

../lib/libpkg.a:
        	cd $(@D); $(MAKE) $(@F)

Forcing a Nested make Command to Run

Because it has no dependencies, this target runs only when the file named ../lib/libpkg.a is missing. If the file is a library archive protected by .PRECIOUS, this could be a rare occurrence. The current make invocation neither knows nor cares about what that file depends on, nor should it. It is the nested invocation that decides whether and how to rebuild that file.

After all, just because a file is present in the file system does not mean it is up-to-date. This means that you have to force the nested make to run, regardless of the presence of the file, by making it depend on another target with a null rule (and no extant file):

Table 4-14 Target Entry for a Nested make Command
# Reliable target entry for a 
# nested make command.

../lib/libpkg.a:  FORCE
        	cd $(@D); $(MAKE) $(@F) 
FORCE:

In this way, make reliably changes to the correct directory ../lib and builds libpkg.a if necessary, using instructions from the makefile found in that directory. These lines are produced by the nested make run:

$ make ../lib/libpkg.a 
cd ../lib; make libpkg.a 
make libpkg.a 
`libpkg.a' is up to date.

The following makefile uses a nested make command to process local libraries that a program depends on.

Table 4-15 Makefile for C Program with User-Supplied Libraries
# Makefile for a C program with user-supplied 
# libraries and nested make commands. 

CFLAGS= -O 

.KEEP_STATE:

functions: main.o data.o ../lib/libpkg.a 
        	$(LINK.c) -o $@ main.o data.o
         ../lib/libpkg.a -lcurses -ltermlib 
         ../lib/libpkg.a:  FORCE
        	cd $(@D); $(MAKE) $(@F) 
FORCE: 

lint: main.ln data.ln 
        	$(LINT.c) main.ln data.ln 
clean: 
        	rm -f functions main.o data.o main.ln data.ln

When ../lib/libpkg.a is up to date, this makefile produces:

$ make 
cc -O -c main.c 
cc -O -c data.c 
cd ../lib; make libpkg.a 
`libpkg.a' is up to date.  
cc -O -o functions main.o data.o ../lib/libpkg.a -lcurses -l   termlib

The MAKEFLAGS Macro

Like the MAKE macro, MAKEFLAGS is also a special case.


Note -

Do not define MAKEFLAGS in your makefiles.


MAKEFLAGS contains flags (that is, single-character options) for the make command. Unlike other FLAGS macros, the MAKEFLAGS value is a concatenation of flags, without a leading `-'. For instance the string eiknp would be a recognized value for MAKEFLAGS, while -f x.mk or macro=value would not.

If the MAKEFLAGS environment variable is set, make runs with the combination of flags given on the command line and contained in that variable.

The value of MAKEFLAGS is always exported, whether set in the environment or not, and the options it contains are passed to any nested make commands (whether invoked by $(MAKE), make, or /usr/bin/make). This insures that nested make commands are always passed the options which the parent make was invoked.

Passing Parameters to Nested make Commands

With the exception of MAKEFLAGS, make imports variables from the environment and treats them as if they were defined macros. In turn, make propagates those environment variables and their values to commands it invokes, including nested make commands.


Note -

The SHELL environment variable is neither imported nor exported to this version of make.


Macros can be defined as command-line arguments, as well as the makefile. This can lead to name-value conflicts when a macro is defined in more than one place, and make has a fairly complicated precedence rule for resolving them.

First, conditional macro definitions always take effect within the targets (and their dependencies) for which they are defined.

If make is invoked with a macro-definition argument, that definition takes precedence over definitions given either within the makefile, or imported from the environment. (This does not necessarily hold true for nested make commands, however.) Otherwise, if you define (or redefine) a macro within the makefile, the most recent definition applies. The latest definition normally overrides the environment.

Lastly, if the macro is defined in the default file and nowhere else, that value is used.

With nested make commands, definitions made in the makefile normally override the environment, but only for the makefile in which each definition occurs; the value of the corresponding environment variable is propagated regardless.

Command-line definitions override both environment and makefile definitions, but only in the make run for which they are supplied. Although values from the command line are propagated to nested make commands, they are overridden both by definitions in the nested makefiles, and by environment variables imported by the nested make commands.

The -e option behaves more consistently. The environment overrides macro definitions made in any makefile, and command-line definitions are always used ahead of definitions in the makefile and the environment. One drawback to -e is that it introduces a situation in which information that is not contained in the makefile can be critical to the success or failure of a build.

To avoid these complications, when you want to pass a specific value to an entire hierarchy of make commands, run make -e in a subshell with the environment set properly (in the C shell):

% (unsetenv MAKEFLAGS LDFLAGS; setenv CFLAGS -g; make -e)

If you want to test the cases yourself, you can use the following makefiles to illustrate the various cases.

# top.mk 

MACRO= "Correct but unexpected." 

top: 
     	@echo "------------------------------ top"   
     	echo $(MACRO) 
     	@echo "------------------------------"  
   	  $(MAKE) -f nested.mk 
     	@echo "------------------------------ clean" 
clean: 
     	rm nested
# nested.mk 

MACRO=nested 

nested: 
        	@echo "------------------------------ nested" 
        	touch nested 
        	echo $(MACRO) 
        	$(MAKE) -f top.mk 
        	$(MAKE) -f top.mk clean

The following is a summary of macro assignment orders:

Table 4-16 Summary of Macro Assignment Order

Without -e 

With -e in effect 

top-level make commands: 

Conditional definitions  

Conditional definitions  

Make command line 

Make command line 

Latest makefile definition 

Environment value  

Environment value  

Latest makefile definition  

Predefined value, if any 

Predefined value, if any  

nested make commands: 

Conditional definitions  

Conditional definitions  

Make command line  

Make command line  

Latest makefile definition 

Parent make cmd. line  

Environment variable  

Environment value  

Predefined value, if any 

Latest makefile definition  

Parent make cmd. line 

Predefined value, if any  

Compiling Other Source Files

Compiling and Linking a C Program with Assembly Language Routines

The makefile in the next example maintains a program with C source files linked with assembly language routines. There are two varieties of assembly source files: those that do not contain cpp preprocessor directives, and those that do.

By convention, assembly source files without preprocessor directives have the .s suffix. Assembly sources that require preprocessing have the .S suffix.


Note -

ASFLAGS passes options for as to the .s.o and .S.o implicit rules.


Assembly sources are assembled to form object files in a fashion similar to that used to compile C sources. The object files can then be linked into a C program. make has implicit rules for transforming .s and .S files into object files, so a target entry for a C program with assembly routines need only specify how to link the object files. You can use the familiar cc command to link object files produced by the assembler:

Table 4-17 Summary of Macro Assignment Order
CFLAGS= -O 
ASFLAGS= -O 

.KEEP_STATE:

driver: c_driver.o s_routines.o S_routines.o 
        	cc -o driver c_driver.o s_routines.o
         S_routines.o

Note that the .S files are processed using the cc command, which invokes the C preprocessor cpp, and invokes the assembler.

Compiling lex and yacc Sources

lex and yacc produce C source files as output. Source files for lex end in the suffix .l, while those for yacc end in .y. When used separately, the compilation process for each is similar to that used to produce programs from C sources alone.

There are implicit rules for compiling the lex or yacc sources into .c files; from there, the files are further processed with the implicit rules for compiling object files from C sources. When these source files contain no #include statements, there is no need to keep the .c file, which in this simple case serves as an intermediate file. In this case you could use .l.o rule, or the .y.o rule, to produce the object files, and remove the (derived) .c files.

For example, the makefile:

CFLAGS= -O 
.KEEP_STATE:

all: scanner parser 
scanner: scanner.o 
parser: parser.o

produces the result shown below.

$ make -n
rm -f scanner.c 
lex -t scanner.l > scanner.c 
cc -O -c -o scanner.o scanner.c 
rm -f scanner.c
yacc parser.y 
cc -O -c -o parser.o y.tab.c 
rm -f y.tab.c

Things become more complicated when you use lex and yacc in combination. In order for the object files to work together properly, the C code from lex must include a header produced by yacc. It might be necessary to recompile the C source file produced by lex when the yacc source file changes. In this case, it is better to retain the intermediate (.c) files produced by lex, as well as the additional .h file yacc provides, to avoid running lex whenever the yacc source changes.


Note -

yacc produces output files named y.tab.c and y.tab.h. If you want the output files to have the same basename as the source file, you must rename them.


The following makefile maintains a program built from a lex source, a yacc source, and a C source file.

CFLAGS= -O 
.KEEP_STATE:

a2z: c_functions.o scanner.o parser.o 
        	cc -o $@ c_functions.o scanner.o parser.o 
scanner.c: 

parser.c + parser.h: parser.y 
        	yacc -d parser.y 
        	mv y.tab.c parser.c 
        	mv y.tab.h parser.h

Because there is no transitive closure for implicit rules, you must supply a target entry for scanner.c. This entry bridges the gap between the .l.c implicit rule and the .c.o implicit rule, so that the dependency list for scanner.o extends to scanner.l. Since there is no rule in the target entry, scanner.c is built using the .l.c implicit rule.

The next target entry describes how to produce the yacc intermediate files. Because there is no implicit rule for producing both the header and the C source file using yacc -d, a target entry must be supplied that includes a rule for doing so.

Specifying Target Groups with the + Sign

In the target entry for parser.c and parser.h, the + sign separating the target names indicates that the entry is for a target group. A target group is a set of files, all of which are produced when the rule is performed. Taken as a group, the set of files comprises the target. Without the + sign, each item listed would comprise a separate target. With a target group, make checks the modification dates separately against each target file, but performs the target's rule only once, if necessary, per make run.

Maintaining Shell Scripts with make and SCCS

Although a shell script is a plain text file, it must have execute permission to run. Since SCCS removes execute permission for files under its control, it is convenient to make a distinction between a shell script and its "source" under SCCS. make has an implicit rule for deriving a script from its source. The suffix for a shell script source file is .sh. Even though the contents of the script and the .sh file are the same, the script has execute permissions, while the .sh file does not. make's implicit rule for scripts "derives" the script from its source file, making a copy of the .sh file (retrieving it first, if necessary) and changing the mode of the resulting script file to allow execution. For example:

$ file script.sh 
script.sh:				ascii text 
$ make script 
cat script.sh > script 
chmod +x script 
$ file script 
script:				commands text

Running Tests with make

Shell scripts are often helpful for running tests and performing other routine tasks that are either interactive or don't require make's dependency checking. Test suites, in particular, often entail providing a program with specific, repeatable input that a program might expect to receive from a terminal.

In the case of a library, a set of programs that exercise its various functions can be written in C, and then executed in a specific order, with specific inputs from a script. In the case of a utility program, there can be a set of benchmark programs that exercise and time its functions. In each of these cases, the commands to run each test can be incorporated into a shell script for repeatability and easy maintenance.

After you have developed a test script that suits your needs, including a target to run it is easy. Although make's dependency checking might not be needed within the script itself, you can use it to make sure that the program or library is updated before running those tests.

In the following target entry for running tests, test depends on lib.a. If the library is out of date, make rebuilds it and proceeds with the test. This insures that you always test with an up-to-date version:

#This is the library we're testing
LIBRARY= lib.a

test: $(LIBRARY) testscript 
        	set -x ; testscript > /tmp/test.\$\$ 

testscript: testscript.sh test_1 test_2 test_3 

#rules for building the library
$(LIBRARY):
        	@ echo Building $(LIBRARY)
        (library-building rules here)

#test_1 ... test_3 exercise various library functions
test_1 test_2 test_3: $$@.c $(LIBRARY) 
        	$(LINK.c) -o $@ $< 

test also depends on testscript, which in turn depends on the three test programs.

This ensures that they too are up-to-date before make initiates the test procedure. lib.a is built according to its target entry in the makefile; testscript is built using the .sh implicit rule; and the test programs are built using the rule in the last target entry, assuming that there is just one source file for each test program. (The .c implicit rule doesn't apply to these programs because they must link with the proper libraries in addition to their .c files).

Escaped References to a Shell Variable

The string \$\$ in the rule for test illustrates how to escape the dollar-sign from interpretation by make. make passes each $ to the shell, which expands the $$ to its process ID. This technique allows each test to write to a unique temporary filename. The set -x command forces the shell to display the commands it runs on the terminal, which allows you to see the actual file name containing the results of the specific test.

Shell Command Substitutions

You can supply shell command substitutions within a rule as in the following example:

do: 
	        @echo `cat Listfile`

You can even place the backquoted expression in a macro:

DO= `cat Listfile` 
do: 
        	@echo $(DO)

However, you can only use this form of command substitution within a rule.

Command-Replacement Macro References

If you supply a shell command as the definition of a macro:

COMMAND= cat Listfile

you can use a command-replacement macro reference to instruct make to replace the reference with the output of the command in the macro's value. This form of command substitution can occur anywhere within a makefile:

COMMAND= cat Listfile 
$(COMMAND:sh): $$(@:=.c)

This example imports a list of targets from another file and indicates that each target depends on a corresponding .c file.

As with shell command substitution, a command replacement reference evaluates to the standard output of the command. NEWLINE characters are converted to SPACE characters. The command is performed whenever the reference is encountered. The command's standard error is ignored. However, if the command returns a non-zero exit status, make halts with an error.

A workaround for this is to append the true command to the command line:

COMMAND = cat Listfile ; true

Command-Replacement Macro Assignment

A macro assignment of the form

cmd_macro:sh = command

assigns the standard output of the indicated command to cmd_macro; for instance:

COMMAND:sh = cat Listfile 
$(COMMAND): $$(@:=.c)

is equivalent to the previous example. However, with the assignment form, the command is only performed once per make run. Again, only the standard output is used, NEWLINE characters are converted to SPACE characters, and a non-zero exit status halts make with an error.

Alternate forms of command-replacement macro assignments are:

macro:sh += command

Append command output to the value of macro.

target := macro:sh = command

Conditionally define macro to be the output of command when processing target and its dependencies.

target := macro:sh += command

Conditionally append the output of command to the value of macro when processing target and its dependencies.