[comp.lang.c++] Managing C++ Libraries: Minimizing Recompilations

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 ------------------------------------------