coggins@retina.cs.unc.edu (Dr. James Coggins) (11/08/88)
Managing C++ Libraries: Minimizing Recompilations Greg Bollella and James Coggins Computer Science UNC-Chapel Hill Using the comparison of timestamps and the dependency structure to determine the set of files that need recompilation after an arbitrary change to a C++ source file hinders the incremental development of large systems in C++ since a single change can unleash a torrent of recompilations. We would like to see a method that could (1) determine the minimal set of files that need recompilation given some arbitrary change and (2) produce a list of files that need changes as a consequence of the original change. We are not aware of any method presently capable of either of the above. (Any pointers would be greatly appreciated!) In the absence of a method for determining the minimum set of files that need recompilation, we propose here a reasonable approximation. Our method depends on the library developer distinguishing two kinds of source code modifications: trivial and non-trivial. Trivial changes do not necessitate any recompilation, and non-trivial changes do necessitate recompilation of the files which are dependent on the file to which the change has been made. The problem of determining the minimal set of files that need recompilation cannot be satisfactorily solved by looking at the dependency structure of the library. For example, suppose that C.c includes A.h and B.h, and assume that A.h has been changed in a trivial way (which would not require C.o to be remade) and B.h has been changed in a non-trivial way (which would require that C.o be remade). If some method for determining which files required compilation that was based only on dependency information were invoked at this point, it would update C.o without recompiling since C.o depends on A.h and the change to A.h was known to be trivial. But, it is clear that this is an error since a non-trivial change was made to B.h (i.e., a request to remake the system at this point would not remake C.o since its timestamp indicates it is newer than B.h). Given a sequence of interleaved trivial and non-trivial changes to the system, dependency information is inadequate to determine a minimum recompilation set. If only the trivial change had been made then basing recompilation on dependency information would work, but since only trivial changes have been made, even this is not necessary. The above example leads to the observation that if a sequence of trivial changes has been made to a library, the timestamps of all of the .o files in the system can be updated (e.g. using touch(1)) to the current time without loss of consistency. Consider the state of the system before the sequence of trivial changes, it is in a 'no recompilation needed' state. By definition the trivial changes have not changed this state so it can do no harm to touch all of the .o files in the system since this action will return the state of the system to 'no recompilation needed'. With this in mind we have included a "trivial" target in the makefiles of Dr. Coggins's library. (See Managing C++ Libraries: Subdirectories and .c Files) This could be accomplished, of course, by a very small shell script, but we prefer this method because of the added control afforded. We can invoke the updating at any of three levels. Invoking at the mainlib level will update the complete system, at the sublibrary level will update related classes and at the class level will update only a single class. We include below the revised Makefiles incorporating the "trivial" command to update the system without recompilation. # MAKEFILE FOR MAINLIB ------------------------------------------- .SILENT A = sublib1 B = sublib2 #C = #D = trivial: echo "Update the complete system" (cd $A; make trivial) (cd $B; make trivial) # (cd $C; make trivial) # (cd $D; make trivial) echo "Update complete" compile: echo "Perform all compilations" (cd $A; make all) (cd $B; make all) # (cd $C; make all) # (cd $D; make all) echo "Compilations complete" cleanup: echo "Cleanup all libraries" (cd $A; make cleanup) (cd $B; make cleanup) # (cd $C; make cleanup) # (cd $D; make cleanup) echo "Cleanup complete" create: echo "Create mainlib.a from scratch" (cd $A; make library) (cd $B; make library) # (cd $C; make library) # (cd $D; make library) touch mainlib.a rm mainlib.a mv newlib.a mainlib.a ranlib mainlib.a echo "mainlib.a complete" # END OF MAKEFILE FOR MAINLIB------------------------------------ # MAKEFILE FOR SUBLIB1 ------------------------------------------ .SILENT LIB = SUBLIB1 MAINLIB= /.../mainlib/newlib.a ( <-- full path name of the .a file) A = ClassA B = ClassB #C = #D = trivial: echo "$(LIB) Update the sublibrary" (cd $A; make trivial) (cd $B; make trivial) # (cd $C; make trivial) # (cd $D; make trivial) echo "$(LIB) Update complete" all: echo "$(LIB) Begin Compilation" (cd $A; make all) (cd $B; make all) # (cd $C; make all) # (cd $D; make all) echo "$(LIB) Compilation Complete" cleanup: echo "$(LIB) Begin Cleanup" (cd $A; make cleanup) (cd $B; make cleanup) # (cd $C; make cleanup) # (cd $D; make cleanup) echo "$(LIB) Cleanup Complete" library: echo "$(LIB) Create library" ar lq $(MAINLIB) $A/*.o ar lq $(MAINLIB) $B/*.o # ar lq $(MAINLIB) $C/*.o # ar lq $(MAINLIB) $D/*.o echo "$(LIB) Create library complete" # END OF MAKEFILE FOR SUBLIB1 ------------------------------------------ # MAKEFILE FOR CLASS CLASSA -------------------------------------------- .SILENT CC=CC CFLAGS= +e0 -fswitch CLASS = CLASSA OBJ = CDest.o CNull.o Cintint.o reset.o \ draw.o compute.o trivial: echo "$(CLASS) Update" touch *.o echo "$(CLASS) Update complete" all: echo "$(CLASS) Begin Compilation" make $(OBJ) echo "$(CLASS) Compilation complete" cleanup: echo "$(CLASS) Cleanup" /bin/rm *.o echo "$(CLASS) Cleanup complete" .c.o: echo "Begin $*.c" $(CC) $(CFLAGS) -c $< # END OF MAKEFILE FOR CLASSA ------------------------------------------