Programming Utilities Guide

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.