Add a script 'check_headers_and_includes' to check that #include directives
are not against the grain.
Wrap this script together with 'check_makefile_consistency' into
'post_regtest_checks' and invoke that from the toplevel Makefile. So we can
easily add new checkers in the future.

Add a new make target 'post-regtest-checks' to just run those checks
and nothing else.


git-svn-id: svn://svn.valgrind.org/valgrind/trunk@13570 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/Makefile.am b/Makefile.am
index 498463e..28087d9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -67,15 +67,17 @@
 	cat $(DEFAULT_SUPP_FILES) >> default.supp
 
 ## Preprend @PERL@ because tests/vg_regtest isn't executable
-## Ensure make exits with error if PERL fails or check_makefile_consistency fails.
+## Ensure make exits with error if PERL fails or post_regtest_checks fails.
 regtest: check
 	gdbserver_tests/make_local_links $(GDB)
 	if @PERL@ tests/vg_regtest gdbserver_tests $(TOOLS) $(EXP_TOOLS) ; then \
-	   tests/check_makefile_consistency gdbserver_tests $(TOOLS) $(EXP_TOOLS); \
+	   tests/post_regtest_checks $(abs_top_srcdir) gdbserver_tests $(TOOLS) $(EXP_TOOLS); \
 	else \
-	   tests/check_makefile_consistency gdbserver_tests $(TOOLS) $(EXP_TOOLS); \
+	   tests/post_regtest_checks $(abs_top_srcdir) gdbserver_tests $(TOOLS) $(EXP_TOOLS); \
 	   false; \
 	fi
+post-regtest-checks:
+	tests/post_regtest_checks $(abs_top_srcdir) gdbserver_tests $(TOOLS) $(EXP_TOOLS)
 nonexp-regtest: check
 	@PERL@ tests/vg_regtest $(TOOLS)
 exp-regtest: check
diff --git a/tests/check_headers_and_includes b/tests/check_headers_and_includes
new file mode 100755
index 0000000..d558412
--- /dev/null
+++ b/tests/check_headers_and_includes
@@ -0,0 +1,292 @@
+#!/usr/bin/env perl
+
+#-------------------------------------------------------------------
+# Check header files and #include directives
+#
+# (1) include/*.h must not include pub_core_...h
+# (2) coregrind/pub_core_xyzzy.h may include pub_tool_xyzzy.h
+#     other coregrind headers may not include pub_tool_xyzzy.h
+# (3) coregrind/ *.c must not include pub_tool_xyzzy.h
+# (4) tool *.[ch] files must not include pub_core_...h
+# (5) include pub_core/tool_clreq.h instead of valgrind.h except in tools'
+#     export headers
+#-------------------------------------------------------------------
+
+use strict;
+use warnings;
+use Getopt::Long;
+
+my $this_script = "check_headers_and_includes";
+
+# The list of top-level directories is divided into three sets:
+#
+# (1) coregrind directories
+# (2) tool directories
+# (3) directories to ignore
+#
+# If a directory is found that does not belong to any of those sets, the
+# script will terminate unsuccessfully.
+
+my %coregrind_dirs = (
+    "include" => 1,
+    "coregrind" => 1,
+    );
+
+my %tool_dirs = (
+    "none" => 1,
+    "lackey" => 1,
+    "massif" => 1,
+    "memcheck" => 1,
+    "drd" => 1,
+    "helgrind", => 1,
+    "callgrind" => 1,
+    "cachegrind" => 1,
+    "exp-bbv" => 1,
+    "exp-dhat" => 1,
+    "exp-sgcheck" => 1
+    );
+
+my %dirs_to_ignore = (
+    ".deps" => 1,
+    ".svn" => 1,
+    ".in_place" => 1,
+    "VEX" => 1,
+    "docs" => 1,
+    "auxprogs" => 1,
+    "autom4te.cache" => 1,
+    "nightly" => 1,
+    "perf" => 1,
+    "tests" => 1,
+    "gdbserver_tests" => 1,
+    "mpi" => 1
+    );
+
+my %tool_export_header = (
+    "drd/drd.h" => 1,
+    "helgrind/helgrind.h" => 1,
+    "memcheck/memcheck.h" => 1,
+    "callgrind/callgrind.h" => 1
+    );
+
+my $usage=<<EOF;
+USAGE
+
+  $this_script
+
+    [--debug]          Debugging output
+
+    dir ...            Directories to process
+EOF
+
+my $debug = 0;
+my $num_errors = 0;
+
+&main;
+
+sub main {
+    GetOptions( "debug"  => \$debug ) || die $usage;
+
+    my $argc = $#ARGV + 1;
+
+    if ($argc < 1) {
+        die $usage;
+    }
+
+    foreach my $dir (@ARGV) {
+        process_dir(undef, $dir, 0);
+    }
+
+    my $rc = ($num_errors == 0) ? 0 : 1;
+    exit $rc;
+}
+
+sub process_dir {
+    my ($path, $dir, $depth) = @_;
+    my $hdir;
+
+    if ($depth == 0) {
+# The root directory is always processed
+    } elsif ($depth == 1) {
+# Toplevel directories
+        return if ($dirs_to_ignore{$dir});
+
+        if (! $tool_dirs{$dir} && ! $coregrind_dirs{$dir}) {
+            die "Unknown directory '$dir'. Please update $this_script\n";
+        }
+    } else {
+# Subdirectories
+        return if ($dirs_to_ignore{$dir});
+    }
+
+    print "DIR = $dir   DEPTH = $depth\n" if ($debug);
+
+    chdir($dir) || die "Cannot chdir '$dir'\n";
+
+    opendir($hdir, ".") || die "cannot open directory '.'";
+
+    while (my $file = readdir($hdir)) {
+        next if ($file eq ".");
+        next if ($file eq "..");
+
+# Subdirectories
+        if (-d $file) {
+            my $full_path = defined $path ? "$path/$file" : $file;
+            process_dir($full_path, $file, $depth + 1);
+            next;
+        }
+
+# Regular files; only interested in *.c and *.h
+        next if (! ($file =~ /\.[ch]$/));
+        my $path_name = defined $path ? "$path/$file" : $file;
+        process_file($path_name);
+    }
+    close($hdir);
+    chdir("..") || die "Cannot chdir '..'\n";
+}
+
+#---------------------------------------------------------------------
+# Given a path name strip leading directories.
+#---------------------------------------------------------------------
+sub basename {
+    my ($path_name) = @_;
+    my $file = $path_name;
+
+    $file =~ s/^.*\///;
+
+    return $file;
+}
+
+#---------------------------------------------------------------------
+# Return 1, if file is located in <valgrind>/include
+#---------------------------------------------------------------------
+sub is_coregrind_export_header {
+    my ($path_name) = @_;
+
+    return ($path_name =~ /^include\//) ? 1 : 0;
+}
+
+#---------------------------------------------------------------------
+# Return 1, if file is located underneath <valgrind>/coregrind
+#---------------------------------------------------------------------
+sub is_coregrind_file {
+    my ($path_name) = @_;
+
+    return ($path_name =~ /^coregrind\//) ? 1 : 0;
+}
+
+#---------------------------------------------------------------------
+# Return 1, if file is located underneath <valgrind>/<tool>
+#---------------------------------------------------------------------
+sub is_tool_file {
+    my ($path_name) = @_;
+
+    for my $tool (keys %tool_dirs) {
+        return 1 if ($path_name =~ /^$tool\//);
+    }
+    return 0
+}
+
+#---------------------------------------------------------------------
+# Return array of files #include'd by file.
+#---------------------------------------------------------------------
+sub get_included_files {
+    my ($path_name) = @_;
+    my @includes = ();
+    my $file = basename($path_name);
+
+    open(FILE, "<$file") || die "Cannot open file '$file'";
+
+    while (my $line = <FILE>) {
+        if ($line =~ /^\s*#\s*include "([^"]*)"/) {
+            push @includes, $1;
+        }
+        if ($line =~ /^\s*#\s*include <([^>]*)>/) {
+            push @includes, $1;
+        }
+    }
+    close FILE;
+    return @includes;
+}
+
+#---------------------------------------------------------------------
+# Check a file from <valgrind>/include
+#---------------------------------------------------------------------
+sub check_coregrind_export_header {
+    my ($path_name) = @_;
+
+    foreach my $inc (get_included_files($path_name)) {
+        $inc = basename($inc);
+# Must not include pub_core_....
+        if ($inc =~ /pub_core_/) {
+            error("File $path_name must not include $inc\n");
+        }
+# Only pub_tool_clreq.h may include valgrind.h
+        if (($inc eq "valgrind.h") && ($path_name ne "include/pub_tool_clreq.h")) {
+            error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
+        }
+    }
+}
+
+#---------------------------------------------------------------------
+# Check a file from <valgrind>/coregrind
+#---------------------------------------------------------------------
+sub check_coregrind_file {
+    my ($path_name) = @_;
+    my $file = basename($path_name);
+
+    foreach my $inc (get_included_files($path_name)) {
+        print "\tINCLUDE $inc\n" if ($debug);
+# Only pub_tool_xyzzy.h may include pub_core_xyzzy.h
+        if ($inc =~ /pub_tool_/) {
+            my $buddy = $inc;
+            $buddy =~ s/pub_tool/pub_core/;
+            if ($file ne $buddy) {
+                error("File $path_name must not include $inc\n");
+            }
+        }
+# Must not include valgrind.h
+        if ($inc eq "valgrind.h") {
+            error("File $path_name should include pub_core_clreq.h instead of $inc\n");
+        }
+    }
+}
+
+#---------------------------------------------------------------------
+# Check a file from <valgrind>/<tool>
+#---------------------------------------------------------------------
+sub check_tool_file {
+    my ($path_name) = @_;
+    my $file = basename($path_name);
+
+    foreach my $inc (get_included_files($path_name)) {
+        print "\tINCLUDE $inc\n" if ($debug);
+# Must not include pub_core_...
+        if ($inc =~ /pub_core_/) {
+            error("File $path_name must not include $inc\n");
+        }
+# Must not include valgrind.h unless this is an export header
+        if ($inc eq "valgrind.h" && ! $tool_export_header{$path_name}) {
+            error("File $path_name should include pub_tool_clreq.h instead of $inc\n");
+        }
+    }
+}
+
+sub process_file {
+    my ($path_name) = @_;
+
+    print "FILE = $path_name\n" if ($debug);
+
+    if (is_coregrind_export_header($path_name)) {
+        check_coregrind_export_header($path_name);
+    } elsif (is_coregrind_file($path_name)) {
+        check_coregrind_file($path_name);
+    } elsif (is_tool_file($path_name)) {
+        check_tool_file($path_name);
+    }
+}
+
+sub error {
+    my ($message) = @_;
+    print STDERR "*** $message";
+    ++$num_errors;
+}
diff --git a/tests/post_regtest_checks b/tests/post_regtest_checks
new file mode 100755
index 0000000..8024f40
--- /dev/null
+++ b/tests/post_regtest_checks
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+#-------------------------------------------------------------------
+#
+# This script is invoked after regression testing has finished
+# It performs various consistency checks.
+#
+# Arguments passed to this script are (from left to right)
+# - absolute path name of valgrind's root directory
+# - list of directories being passed to check_makefile_consistency
+#
+#-------------------------------------------------------------------
+
+# echo "$@"
+
+abs_top_srcdir="$1"
+test_dir="$abs_top_srcdir/tests"
+shift
+
+errors=0
+
+#-------------------------------------------------------------------
+
+echo "...checking makefile consistency"
+$test_dir/check_makefile_consistency "$@"
+if [ $? != 0 ]; then
+   errors=1
+fi
+
+#-------------------------------------------------------------------
+
+echo "...checking header files and include directives"
+$test_dir/check_headers_and_includes "$abs_top_srcdir"
+if [ $? != 0 ]; then
+   errors=1
+fi
+
+exit $errors