[net.sources] make: construction of "#include dependencies"

svb@CS-Arthur (Stephan v. Bechtolsheim) (11/29/83)

What follows is
	1. An excerpt from the manual page provided containing the
	   motivation
	2. The manual page (use -man macros).
	3. The shell script itself.
I would appreciate feedback. Please drop me a short note,
if you are using the program. Also let me know about improvements.
Thank you.

Stephan Bechtolsheim
svb @ purdue

============================================================================

Maketd computes dependencies for makefiles from sources introduced through
include files.  It generates lines like
	 xx.o: e.h struct.h ../h/const.h ...
It makes xx.o not only dependent on all files it includes,
but also recursively on all files other files include.

This is achieved by running the whole source through the C preprocessor which
generates information about which file is included into which file
at what line.  This information is extracted to generate the dependency
lines.

The motivation to write this program stems from the fact that make
does not recognize transitive dependencies.  Example:
assume the following makefile:

       xx.o:   e.h

       e.h:    struct.h ../h/const.h

A change of struct.h will not trigger a recompilation of xx.o.


==============================================================================
.BE
.if n \{.de Q
"\\$1"\\$2
.\}
.if t \{.de Q
``\\$1''\\$2
.\}
..
.TH MAKETD 1
.UC 4
.SH NAME
maketd \- make transitive dependencies
.SH SYNOPSIS
.B maketd
[ option ... ]
[
.I file ...
]
.SH DESCRIPTION
.I Maketd
computes dependencies for makefiles from sources introduced through
include files.  It generates lines
like
.Q "xx.o: e.h struct.h ../h/const.h ..." .
It makes xx.o not only dependent on all files it includes,
but also recursively on all files other files include.
.PP
This is achieved by running the whole source through the C preprocessor which
generates information about which file is included into which file
at what line.  This information is extracted to generate the dependency
lines.
.PP
The motivation to write this program stems from the fact that make
does not recognize transitive dependencies.  Example:
assume the following makefile:
.nf

       xx.o:   e.h

       e.h:    struct.h ../h/const.h

.fi
A change of
.I struct.h
will
.B not
trigger a recompilation of xx.o.
.PP
The directories used in the search for include files
are identical to the ones used by the C-compiler, because the
C preprocessor is used.
This also means that `#define's, `#ifdef's, etc. are evaluated.
It may therefore be necessary to recompute the dependencies if
any source has been changed, and especially if
.I CFLAGS
in the associated makefile has been changed.
.PP
A typical application in a makefile goes as follows:
.nf

	SOURCE = a.c b.c c.c
	INCLUDES =  \-I../include  \-I../h
	CFLAGS = \-DPURDUE \-DBSD4.2 $(INCLUDES)

	maketd:
		maketd $(CFLAGS) $(SOURCE)

	# DO NOT DELETE THIS LINE \- make maketd DEPENDS ON IT

.fi
The generated dependencies will be inserted after the `# DO NOT DELETE...'
line.  Everything after this line will go away through the editing
process of the makefile.
The default filename for the makefile is `Makefile', which can
be changed with the -m option.
Before it is edited, the makefile will be saved in `Makefile.bak'.
.PP
Several options apply:
.TP
.BI \-a
Normally, dependencies on files in `/usr/include' are
not included \- this option also includes dependencies on those files.
.TP
.BI \-m file
Instead of editing `Makefile', the file named
.I file
is edited.
.TP
.BI \-o directory
Normally dependencies are of the form
.Q "a.o: ....." .
This option generates dependencies of the form
.Q "\fIdirectory\fP/a.o:...." ,
which is useful for makefiles which store the objects in
a separate subdirectory.
The name of the directory must not be empty.
.TP
.B \-I... \-D.... \-U....
These options are identical to the same options of
.IR cc (1).
.TP
.BI \-d
Instead of editing the makefile, dependencies are written to standard
output.
.SH "SEE ALSO"
make(1), cc(1)
.SH "AUTHOR"
Stephan v. Bechtolsheim (svb)

==============================================================================
#! /bin/sh
PATH=/usr/local/bin:/bin:/usr/bin:/usr/ucb
#       Author: Stephan v. Bechtolsheim
#       svb @ purdue
#
#       This is a shell script which runs the C preprocessor on every
#       source and this way finds the transitive dependencies
#       of the source on all include files.
#       The program respects #defines and #ifdefs etc. given in the
#       source or on the command line.
#
#       Options:
#       (a) options specific to this shell script
#               -a:     /usr/include.....
#               -m:     Instead of $MAKEFILE whataver follows '-m'
#                       is used as Makefile name to be
#                       edited.
#               -d:     Generate dependencies only and output on
#                       standard output. Don't edit makefile.
#       (b) the following options are identical to the options used
#               by the C preprocessor:
#               -I....
#               -D...
#               -U...
#       (c) other options
#               are ignored
#

progname=$0
# Name of the Makefile which will be edited to add the dependencies
MAKEFILE=Makefile
if [ $# = 0 ] ; then
    cat << EOF
usage:  $progname [-ad] [-m<file>] [-o<directory>] [-I...] [-D...] [-U...] <file> ...
	-a:     include dependencies on files in directory /usr/include
	-m:     to specify another file to be edited as Makefile
		(instead of $MAKEFILE)
	-d:     dependencies to standard output, no editing of $MAKEFILE
	-o:     Dependencies will be of the form <directory>/a.o: ....
		(instead of simply a.o:.....)
	-I, -D, -U:     as in the C preprocessor
EOF
exit
fi

TMPFILE=/tmp/mtd1$$.tmp
touch $TMPFILE
TMPFILE2=/tmp/mtd2$$.tmp
DEPFILE=/tmp/mtd3$$.tmp
touch $DEPFILE
#       In EDD we generate a editor script to edit Makefile
EDD=/tmp/mtd4$$.tmp

trap 'rm -f $TMPFILE $TMPFILE2 $DEPFILE $EDD ; exit ' 1 2 3 15

#       The crucial line - everything after this line will
#       disappear in the Makefile and will contain the mechanically
#       generated dependencies.
CLINE='# DO NOT DELETE THIS LINE - make maketd DEPENDS ON IT'

# For the -a, -d options of this program.
AOPTION=0
DOPTION=0
OBJDIR=
# Collect in OPTIONS all options you want to pass on to the C preprocessor.
OPTIONS=
for i in $*; do
    case $i in
#       ****************
#       OPTION business
#       ****************
	-a)
	    AOPTION=1
	;;
	-d)
	    DOPTION=1
	;;
	-m*)
	    MAKEFILE=`expr $i : '-m\(.*\)'`
	;;
	-o*)
	    if test $i = '-o' ; then
		echo    "$progname: -o option requires directory name"
		exit 1
	    fi
	    OBJDIR=`expr $i : '-o\(.*\)'`
	    if test ! -d $OBJDIR ; then
		echo    "$progname: -o option: \"$OBJDIR\" is no directory"
		exit 1
	    fi
	    OBJDIR="$OBJDIR/"
	;;
	-[I,U,D]*)
	    OPTIONS=" $OPTIONS $i"
	;;

	-*)
	    echo        "$progname: option \"$i\" unknown, ignored"
	;;


#       ****************
#       source file name business
#       ****************
	*)
	    SOURCE=$i
	    if test ! -r $SOURCE ; then
		echo    "$progname: file \"$SOURCE\" does not exist, skipped"
		continue
	    fi
#       Run everything through the preprocessor, collect only lines
#       which identify that an insert step has been executed by the C preproc.
#       Remove from those lines all crep we don't need.
#       Then sort this output so we can remove duplictate lines.
	    SOURCE_=`echo $SOURCE | sed -e 's,/,\\\/,g'`
#           echo "SOURCE is \"$SOURCE\", SOURCE_ is \"$SOURCE_\""
	    /lib/cpp $OPTIONS $SOURCE | grep '^#' | \
		sed -e 's,^#[ ]*[0-9]*[ ]*,,' -e 's,",,g' \
		    -e "/$SOURCE_/d" | \
		sort | \
		awk  '{ if ($0 != prev) print $0 } { prev = $0 }' \
		> $TMPFILE
#       If there is no '-a' option, we don't need dependencies on files
#       on files in '/usr/include'. Remove these.
	    if [ $AOPTION = 0 ] ; then
		sed -e '/\/usr\/include/d' $TMPFILE > $TMPFILE2
		mv $TMPFILE2 $TMPFILE
	    fi

#       Generate a nice output now......
#       OBJECT: The Source filename with extension '.o' now.
	    OBJECT=$OBJDIR`expr $SOURCE : '\(.*\)\..*'`.o
#           echo "Object is \"$OBJECT\""

#       Now the dependencies, and it checks whether the next dependency
#       still fits on the same line. If not a new line is started.
#       Instead of './struct.h' write 'struct.h' only.
	    awk "BEGIN {printf \"$OBJECT:\t\" ; le=length(\"$OBJECT\")+8} \
		{if ((le+length(\$0))>=80) {printf \"\\ \n\t\"; le=8}} \
		{printf \" %s  \", \$0 } \
		{le += length(\$0)+3} \
		END {print}     \
		"  $TMPFILE |   \
		sed -e 's,\\ ,\\,g' -e 's,//,/,g' -e 's, \./,,g' >> $DEPFILE
	;;
    esac
done

#       At this point the dependencies are stored in DEPFILE.
if test $DOPTION = 1 ; then
    cat $DEPFILE
    rm -f $DEPFILE $TMPFILE $TMPFILE2
    exit
fi

#       ******************************
#       Now start editing the Makefile
#       ******************************
if test ! -w $MAKEFILE ; then
    echo        "$progname: can't edit $MAKEFILE"
    exit 1;
fi

# We now append $CLINE to the Makefile - this way we make sure
# that editing the Makefile does not fail below. If the Makefile
# already had such a line it it, it does not matter - the line
# will simply go away later.
echo    ''                                              >> $MAKEFILE
echo    "$CLINE"                                        >> $MAKEFILE

# Build the editor script to edit the Makefile later.
echo    "/$CLINE/,\$d"                                  >> $EDD
echo    "\$a"                                           >> $EDD
echo    $CLINE                                          >> $EDD
echo    '# Dependencies generated at: '`date`           >> $EDD
echo    ''                                              >> $EDD
cat $DEPFILE                                            >> $EDD
echo    ''                                              >> $EDD
echo    '# DO NOT ADD ANYTHING HERE - WILL GO AWAY'     >> $EDD
echo    '.'                                             >> $EDD
echo    'w'                                             >> $EDD
echo    'q'                                             >> $EDD

# Editor scipt done, edit makefile now.
cp $MAKEFILE $MAKEFILE.bak
ed - $MAKEFILE < $EDD

rm -f $TMPFILE $TMPFILE2 $DEPFILE $EDD
==============================================================================