Subject: Program to list (and comprehend) nested includes

From: [email-masked] (Alan Stebbens)
Date: 1 May 91 23:57:43 GMT

When trying to port software to the IBM RS3000 running AIX 3.1, it
often occurs that I have to isolate a missing or malformed rerenced to
some "standard" macro or define.  Also, there are many include files in
the /usr/include hierarchy which are ultimately recursive by
re-invoking, for example, /usr/include/standards.h.  It becomes very
difficult to understand the dependencies of these include files when
such recursion occurs is several places or is very deep.

The following program, "includes", is my "quick 'n dirty" solution to
this problem:  it will list, and optionally indent and line-number, the
standard `cpp' include directives.

Oh yeah, a caveat: I had originally intended the program to be able to
dynamically interpret #define's and #undef's, but never followed up
with the code to do the job.  Thus, the internal documentation and
"usage:" show a -D and -U option usage, but don't really do anything
with it.  Perhaps, some other time, I'll make it work as intended.  Or,
someone else will.

Enjoy.

Alan Stebbens        <[email-masked]>             (805) 893-3221
     Center for Computational Sciences and Engineering (CCSE)
          University of California, Santa Barbara (UCSB)
           3111 Engineering I, Santa Barbara, CA 93106

============================= cut here ===================================
#!/bin/perl
# includes [[-[vincu]...] [-Idir] [-Dname] [-Uname]]... files
#
# Given a list of files, determine the #include dependency list
# given '-c', use CPP to determine the _dynamic_ dependency list,
# otherwise, the static include file dependency list is produced.
#
# If the `-D' or `-U' options are given, `-c' is assumed.
#
#
$IndentStr = ' ' x 4;
@IncludeDirs = ('.');
@Defines = ();
$Verbose = 0;
$Pathnames = 0;
$LineNums = 0;
$Indent = 0;
$SortUniq = 0;
&Usage unless $#ARGV >= $[;
while ($_ = shift) { 	# scan arguments
    if (!/^-/) {
	unshift(@ARGV,$_);
	last;
    }
    while (s/^-(.)/-/) {
	$Compile++ 			if $1 eq 'c';
	$Indent++			if $1 eq 'i';
	$LineNums++			if $1 eq 'n';
	$Pathnames++			if $1 eq 'p';
	$SortUniq++			if $1 eq 's';
	$Verbose++			if $1 eq 'v';
	if ($1 =~ /[IDU]/) {
	    $opt = $1;
	    s/^-(.*)/-$opt$1/;		# put option back in
	    push(@IncludeDirs,$_)	if $opt eq 'I';
	    push(@Defines,$_)		if $opt =~ /DU/;
	    last;
	}
	&Usage unless $1 =~ /[cinpsv]/;
    }
}
&Usage if $SortUniq && ($LineNums || $Indent);
$Compile++ if $#Defines >= $[;		# any defines? compile it
$Pathnames = $LineNums = $Indent = 1 if $Verbose;
@Files = @ARGV;
while ($File = shift(@Files)) {
    printf "%s:\n",$File;
    if ($SortUniq) {
	select(STDOUT); $| = 1;
	open(SORT,"|sort -u +1 -2") || die "Can't open pipe to `sort -u' filter because $!\n";
	select(SORT);	$| = 1;
    }
    &ShowIncludes($File,0);
    if ($SortUniq) {
	close(SORT);
	select(STDOUT);
    }
}
exit;

%Included = ();
sub ShowIncludes {
    local($name,$level) = @_;
    local(*FILE);
    local($_,$.);
    local($pathname,$lq,$incname,$rq);
    local($indent) = $Indent ? $IndentStr x $level : '';

    if (open(FILE,$name)) {
	$Included{$name}++;
	while (<FILE>) {
	    next unless /^#include/;
	    chop;
	    if (/^#include\s*(")([^"]*)(")/ ||
		/^#include\s*(<)([^>]*)(>)/) {
		($lq,$incname,$rq) = ($1,$2,$3);
		print $indent;
		printf "%3d:",$. if $LineNums;
		printf "#include %s%s%s",$lq,$incname,$rq;
		if ($file = &FindFile($incname,($lq eq '"'))) {
		    printf "\t(%s)",$file if $Pathnames;
		    print "\n";
		    &ShowIncludes($file,$level+1) unless $Included{$file};
		} else {
		    printf ": can't find $incname\n";
		}
	    } else {
		print ": bad syntax.\n";
	    }
	}
	close FILE;
    } else {
	print ": can't open $name because $!\n";
    }
}

sub FindFile {
    local($file,$localflag) = @_;
    local($path);
    if ($localflag) {
	foreach $dir (@IncludeDirs) {
	    ($path = $dir . '/' . $file) =~ s/^-I//;
	    return $path if -f $path;
	}
    }
    foreach $dir ('/usr/local/include', '/usr/include') {
	return $path if -f ($path = $dir . '/' . $file);
    }
    '';
}

sub Usage {
    select(STDERR);
    print STDERR <<"EOF";
usage: includes [-cinspv] [-Ipath]... [-[D|U]name]... files
Prints the '#include' statements referenced by the named files, automatically
following nested #includes; statically recursive #includes are detected.
 -c	Compile (interpret) #ifdef's and expressions
 -i	Indent nested #includes
 -n	Print the line numbers of the #include statements
 -p	Print the absolute path names for each include file
 -s	Run the #includes through a `sort -u' filter
 -v	Verbose mode; implies `-inp' options
 -Ipath Add `path' to the include search path.
 -Dname	Define `name'; implies the `-c' option.
 -Uname	Undefine `name'; implies the `-c' option.
Sorting is mutually exclusive with indents and line numbering.
EOF
    exit 1;
}
  
============================= cut here ===================================
--

Alan Stebbens