Using Sun WorkShop

Limitations on Makefiles

Concurrent building of multiple targets places some restrictions on makefiles. Makefiles that depend on the implicit ordering of dependencies may fail when built concurrently. Targets in makefiles that modify the same files may fail if those files are modified concurrently by two different targets. Some examples of possible problems are discussed in this section.

Dependency Lists

When building targets concurrently, it is important that dependency lists be accurate. For example, if two executables use the same object file but only one specifies the dependency, then the build may cause errors when done concurrently. For example, consider the following makefile fragment:


all: prog1 prog2 
prog1: prog1.o aux.o 
	$(LINK.c) prog1.o aux.o -o prog1 
prog2: prog2.o 
	$(LINK.c) prog2.o aux.o -o prog2 

When built serially, the target aux.o is built as a dependent of prog1 and is up-to-date for the build of prog2. If built in parallel, the link of prog2 may begin before aux.o is built, and is therefore incorrect. The .KEEP_STATE feature of make detects some dependencies, but not the one shown above.

Explicit Ordering of Dependency Lists

Other examples of implicit ordering dependencies are more difficult to fix. For example, if all of the headers for a system must be constructed before anything else is built, then everything must be dependent on this construction. This causes the makefile to be more complex and increases the potential for error when new targets are added to the makefile. The user can specify the special target .WAIT in a makefile to indicate this implicit ordering of dependents. When dmake encounters the .WAIT target in a dependency list, it finishes processing all prior dependents before proceeding with the following dependents. More than one .WAIT target can be used in a dependency list. The following example shows how to use .WAIT to indicate that the headers must be constructed before anything else.

 all: hdrs .WAIT libs functions

You can add an empty rule for the .WAIT target to the makefile so that the makefile is backward-compatible.

Concurrent File Modification

You must make sure that targets built concurrently do not attempt to modify the same files at the same time. This can happen in a variety of ways. If a new suffix rule is defined that must use a temporary file, the temporary file name must be different for each target. You can accomplish this by using the dynamic macros $@ or $*. For example, a .c.o rule that performs some modification of the .c file before compiling it might be defined as:


.c.o:
    awk -f modify.awk $*.c > $*.mod.c
    $(COMPILE.c) $*.mod.c -o $*.o
    $(RM) $*.mod.c

Concurrent Library Update

Another potential concurrency problem is the default rule for creating libraries that also modifies a fixed file, that is, the library. The inappropriate .c.a rule causes dmake to build each object file and then archive that object file. When dmake archives two object files in parallel, the concurrent updates will corrupt the archive file.


.c.a:
    $(COMPILE.c) -o $% $<
    $(AR) $(ARFLAGS) $@ $%
    $(RM) $%

A better method is to build each object file and then archive all the object files after completion of the builds. An appropriate suffix rule and the corresponding library rule are:


.c.a:
    $(COMPILE.c) -o $% $<

lib.a: lib.a($(OBJECTS))
    $(AR) $(ARFLAGS) $(OBJECTS)
    $(RM) $(OBJECTS)

Multiple Targets

Another form of concurrent file update occurs when the same rule is defined for multiple targets. An example is a yacc(1) program that builds both a program and a header for use with lex(1). When a rule builds several target files, it is important to specify them as a group using the + notation. This is especially so in the case of a parallel build.


y.tab.c y.tab.h: parser.y 
    $(YACC.y) parser.y

This rule is actually equivalent to the two rules:


y.tab.c: parser.y
	$(YACC.y) parser.y
y.tab.h: parser.y
	$(YACC.y) parser.y

The serial version of make builds the first rule to produce y.tab.c and then determines that y.tab.h is up-to-date and need not be built. When building in parallel, dmake checks y.tab.h before yacc has finished building y.tab.c and notices that y.tab.h does need to be built, it then starts another yacc in parallel with the first one. Since both yacc invocations are writing to the same files (y.tab.c and y.tab.h), these files are apt to be corrupted and incorrect. The correct rule uses the + construct to indicate that both targets are built simultaneously by the same rule. For example:


y.tab.c + y.tab.h: parser.y
	$(YACC.y) parser.y