: # *-*-perl-*-*
  eval 'exec perl -S  $0 ${1+"$@"}' 
    if 0;  # if running under some shell
#
# hsrun - automake-compliant tool for gathering info about headers
#          Copyright (c) 2001, Robert Munafo
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#   To contact one of the authors, send electronic mail to:
#   hsrun@mrob.com or paper mail to: Hsrun, c/o Robert Munafo, P.O.
#   Box 0513, Scituate, MA 02066-0513, USA.
#
#
# This script can be added to nearly any project that uses the automake
# and autoconf tools. It is useful for tracking down cross-platform
# header-file problems. You can add it to your project, distribute it
# to your testers, and ask your testers to run it; it will gather
# information on all the header files that are used by your project and
# tell which onces give warnings and errors when used on their system.
# This info is printed in a very concise format to the file "headerstats.out"
# which can then be emailed back to you. It also generates "headerstats.log",
# giving a complete record of everything that was tested and all error and
# warning messages that were seen in the process.
#
# hsrun detects:
#    - missing header files
#    - header files that depend on other header files
#    - header files that give warnings whenever they are #included
#    - header files that give warnings only if another header file is
#      #included after it
#    - header files that give errors whenever they are #included
#    - header files that give errors only if another header file is
#      #included after it
# The output file includes a complete list of the header files, their
# dependencies, the results of each of the above tests and the names of
# the other header files with which they generate warnings or errors.
#
#
#         Instructions for Adding hsrun to Other Projects
#
# Since hsrun is for finding out about header file problems on other systems,
# you have to add it to your distribution and actually distribute it so that
# your users can run it for you and give you headerstats.out files. Thus,
# these instructions are geared with that goal in mind. If you just want to
# get headerstats for your own system, you can apply steps 4 and 5 to the
# Makefiles (not Makefile.am) and skip step 6
#
# 1. Go into the directory where you would type "./configure"
#
# 2. Check to see if each of the following files exists. (If any are missing,
#    the project is not set up for automake and autoconf and these steps
#    won't work, you will have to figure it out on your own)
#         configure
#         config.cache
#         Makefile.am
#         <srcpath>/Makefile.am
#
#    "<srcpath>" should be a subdirectory, or a subdirectory of a
#    subdirectory, but cannot be nested any deeper. hsrun will only examine
#    system headers used by C and C++ files within this source directory.
#
# 3. Put a copy of this script in the <srcpath> directory and call it "hsrun".
#    Make sure the copy is executable (chmod +x hsrun)
#
# 4. Add the following to the end of Makefile.am:
#
#        headerstats:
#        <tab>   (cd src && $(MAKE) $@)
#
#    (except, type a TAB character instead of "<tab>")
#
# 5. Add the following to the end of <srcpath>/Makefile.am:
#
#        headerstats:
#        <tab>   echo 'COMPILE = ' $(COMPILE) >> rc.headerstats
#        <tab>   echo 'LIBS = '    $(LIBS)    >> rc.headerstats
#        <tab>   echo 'LINK = '    $(LINK)    >> rc.headerstats
#        <tab>   ./hsrun
#
#    (again, type TAB characters instead of "<tab>")
#
#    Also in <srcpath>/Makefile.am, find the EXTRA_DIST setting and add
#    hsrun to the list. If there is no EXTRA_DIST setting, add one:
#
#        EXTRA_DIST = hsrun
#
# 6. Rebuild your configure script and makefiles. The steps for this
#    depend a bit on which features of the auto-tools you're using, but
#    it usually goes something like this:
#        aclocal -I macros
#        autoconf
#        automake -a
#        rm config.cache
#        ./configure
#
# 7. Type "make headerstats", either from within the <srcpath> subdirectory
#    or from the top-level directory. Regardless of where you type
#    "make headerstats", the output goes into <srcpath>/headerstats.out
#    A log (similar to config.log) will also be written to
#    <srcpath>/headerstats.log -- this is useful for figuring out new
#    or unusual clashes and/or warnings.

$terminology = <<quote_eof;
Terminology:

  The "dependency list" for a header file is the shortest list of
  headers that can be included before including X in order to
  avoid errors. For any given header there can be multiple alternate
  dependency lists (each with the same number of items).

  A header file X "depends on" another header file Y if Y is a member
  of a dependency list of X.

  If a header file has an empty dependency list (if it does not
  depend on any other header files), it is a "level 1" header file. These
  are commonly called "self-contained" or "stand-alone" header files.

  If a header file X has a dependency list that consists entirely of
  level 1 headers, then X is a "level 2" header file.

  If all possible dependency lists for header file X contain header
  files of levels 1 and 2 (including at least one of level 2) then
  X it is a "level 3" header file. Higher levels are defined
  similarly.

  A "clash tuple" (X,Y) indicates that header files X and Y cause an
  error if you try to include them both in that order.

  A "clash pair" (X,Y) is two headers that belong to a clash tuple,
  in either order. "warning pair" is defined similarly.

  A "warning tuple" (X,Y) indicates that header files X and Y produce
  a compiler warning if you include them in that order.
quote_eof

$legend = <<quote_eof;
Legend:

 e   existence tests (0 = not found on this system; nonzero = found)
 L   level (negative if it clashes with another header of same level)
 wa  warnings: this header always gives compiler warnings
 oo  once-only: this header must not be included twice
quote_eof

# Revision history:
# 20010902 Created first version
# 20010903 Added second existence test based on config.cache
# 20010903 Add &reduce3 -- this decreases number of steps by about 90% when running on systems with a single clash tuple at level 1 (e.g. RedHat 7.1)
# 20010907 Generate headerstats.log; clean up temp files; add dumperr. Check opposite-order tuple after reduce3.
# 20010910 Detect distribution name and version if running under Linux. Fix bugs in clash_cat and reduce3.

sub prnt
{
  local($s) = @_;

  print LOG $s;
  print $s;
}

sub quote1
{
  local($s) = @_;

  $s =~ s/\"/\\\"/g; # "
  return $s;
}

sub wrap1
{
  local($prefix, $l) = @_;
  local($r, $tnl);

  $r = ""; $tnl = "";
  while($l =~ m/^(.{70})(.*)$/) {
    $r = $r . $prefix . $1 . "\n";
    $l = $2;
    $tnl = "\n";
  }
  $r = $r . $prefix . $l . $tnl;
  return $r;
}

sub dumperr
{
  local($cmd, $err, $cmdinfo, $sout, $serr) = @_;
  local($l);

  if ($err) {
    print LOG "\n";
    print LOG ".----\n";
    print LOG &wrap1("| ", "$cmdinfo\n");
    print LOG &wrap1("| ", "cmd: $cmd\n");;
    print LOG "| ret: $err\n";
    print LOG "| STDOUT:\n";
    open(DE_TMP, "$sout");
    while($l = <DE_TMP>) {
      print LOG &wrap1("| ", $l);
    }
    close DE_TMP;
    print LOG "| STDERR:\n";
    open(DE_TMP, "$serr");
    while($l = <DE_TMP>) {
      print LOG &wrap1("| ", $l);
    }
    close DE_TMP;
    print LOG "`----\n";
  }
}

sub try_link
{
  local($compile_only, $warnings_count, $prog, @headers) = @_;
  local($n, $i);

  $n = $#headers;

  # &prnt("\ntry.link @headers\n");

  # Create the test program

  open(OUT, "> $tname_c");
  print OUT "/* -*-C-*-\n";
  print OUT " *\n";
  print OUT " * hsrun-temp.c\n";
  print OUT " *\n";
  print OUT " * THIS IS AN AUTO-GENERATED FILE: DO NOT EDIT\n";
  print OUT " *\n";
  print OUT " * This file may be copied and distributed freely under the terms of the\n";
  print OUT " * GNU General Public License, version 2.0\n";
  print OUT " *\n";
  print OUT " * This file is compiled to test for the existence and/or interdependencies\n";
  print OUT " * of the include files. */\n";
  print OUT "\n";
  for($i=0; $i<=$n; $i++) {
    if ($headers[$i] ne "") {
      print OUT ("#include <" . $headers[$i] . ">\n");
    }
  }
  print OUT "\n";
  print OUT "$prog\n";
  close OUT;

  # First try the compile
  $cmd = $vars{"COMPILE"};
  if ($warnings_count && $g_is_gcc) {
    $cmd .= " -Werror ";
  }
  $cmd .= " $g_extra_ccopts ";
  $cmd .= " -c $tname_c ";
  $cmd .= " >hsrun-tout 2>hsrun-terr";
  # &prnt("$cmd\n");
  $counter++;
  $ret = system($cmd);

  &dumperr($cmd, $ret, "try_link(@headers)", "hsrun-tout", "hsrun-terr");

  # &prnt("ret $ret\n");
  if ($ret) {
    return 0;
  }

  if ($compile_only) {
    return 1;
  }

  # Now try the link
  $cmd = $vars{"LINK"};
  $cmd =~ s/headerstats/$tname/;
  $cmd .= " $tname_o " . $vars{"LIBS"};
  $cmd .= " >hsrun-tout 2>hsrun-terr";
  # &prnt("$cmd\n");
  $counter++;
  $ret = system($cmd);

  &dumperr($cmd, $ret, "try_link(@headers)", "hsrun-tout", "hsrun-terr");

  # &prnt("ret $ret\n");
  if ($ret) {
    return 0;
  }

  # Success!
  return 1;
}

# Given one or more headers, in a given order, returns 1 if a program
# that includes that set of headers in that order will compile and link
# successfully.
sub try_headers
{
  local($warnings_count, @headers) = @_;
  local($prog);

  $prog = "int main(int argc, char *argv[])\n"
        . "{\n"
        . "  return(0);\n"
        . "}\n";
  return(&try_link(0, $warnings_count, $prog, @headers));
}

# Tests to see if compiler is gcc.
sub test_gcc
{
  local($prog, $rv);

  if ($g_is_gcc ne "") {
    return $g_is_gcc;
  }

  # This program compiles successfully if and only if the compiler is gcc.
  $prog = "int main(int argc, char *argv[])\n"
        . "{\n"
        . "#ifndef __GNUC__\n"
        . "  undef_fubar undef_baxqux;\n"
        . "#endif\n"
        . "  return(0);\n"
        . "}\n";
  if (&try_link(0, 0, $prog, ())) {
    $g_is_gcc = 1;
  } else {
    $g_is_gcc = 0;
  }
  return $g_is_gcc;
}

# Tests to see if we can test for warnings
sub test_testwarn
{
  local($prog);
  local($ret);

  if (&test_gcc()) {
    $prog = "#define FUBAR_BAZQUX(x) ((x) + 1)\n"
          . "#define FUBAR_BAZQUX(y) ((y) / 2)\n"
          . "int main(int argc, char *argv[])\n"
          . "{\n"
          . "  return(0);\n"
          . "}\n";
    # Try even hard to get warnings, GCC does not always report redefinitions
    # as warnings depending on what version of GCC you've installed and other
    # things in the environment
    $g_extra_ccopts = "-pedantic";
    $ret = &try_link(0, 1, $prog, ());
    $g_extra_ccopts = "";
    if ($ret) {
      # Program worked, which means we did not detect the warning
      return 0;
    } else {
      # Yup, we detected the warning
      return 1;
    }
  } else {
    return 0;
  }
}

sub map_pat
{
  local($p, $fn) = @_;

  $p =~ s/\<dp\>/\.\+/g;
  $p =~ s/\<fn\>/$fn/g;

  return $p;
}

# Tests to see if a given header file exists
#
# The bits in the return value are:
#  4  - assumed existence because no real tests are available
#  2  - exists according to config.cache
#  1  - exists according to lack of a compilation error
sub test_have
{
  local($ent) = @_;
  local($prog, $trypat, $h_l, $h_c);

  if($g_tth == 0) {
    # We can't test via any mathod, so we'll have to just assume it exists
    # and move on
    return 4;
  }

  # Test via compiler output
  $prog = "int main(int argc, char *argv[])\n"
        . "{\n"
        . "  return(0);\n"
        . "}\n";
  if (&try_link(1, 0, $prog, $ent)) {
    # Program worked, so the file must exist
    $h_l = 1;
  } else {
    if ($g_tth & 1) {
      # Didn't work, must now check why
      $trypat = &map_pat($g_thpat, $ent);
      $h_l = 1;
      open(THIN, "cat hsrun-tout hsrun-terr |");
      while ($$h_l && ($l = <THIN>)) {
        chop $l;
        if ($l =~ m/$trypat/i) {
          $h_l = 0;
        }
      }
      close THIN;
    } elsif ($g_tth) {
      # There is another way to test, so we'll allow that test to determine
      # the answer.
      $h_l = 0;
    } else {
      # There are no ways to test (this is already caught above)
      $h_l = 1;
    }
  }

  # Test via config.cache
  $cvn = $ent;
  $cvn =~ tr/A-Z./a-z_/;
  if ($cc_exists{$cvn} eq "") {
    # configure did not test for this
    $h_c = 0;
  } elsif ($cc_exists{$cvn}) {
    # configure tested for this and found it does exist
    $h_c = 2;
  } else {
    # configure tested for this and found it doesn't exist
    $h_c = 0;
  }

  return($h_l | $h_c);
}

# Tests to see if we can test for existence of header files by looking at
# compiler output
sub test_testhave
{
  local($ent) = @_;
  local($prog, $l, $i);

  # If g_thpat has been set, then this routine has already been run once,
  # we already know the answer and don't have to do the tests.
  if ($g_thpat ne "") {
    return $g_tth;
  }

  # There are two ways to test for existence: performing a test-compile,
  # and examining the output of ./configure. We try to use each of these
  # because there are rare cases in which the tests fail to detect an
  # existent file.
  #
  # The compile test can fail to detect if the pattern is too general.
  # For example, when testing for <foo.h>, if the pattern is
  # /foo.h.+no such file/i, and if <foo.h> includes <otherfoo.h>, and
  # if <otherfoo.h> does not exist, the compiler will report
  # "...otherfoo.h: No such file..." which matches the pattern, giving a
  # false negative test result for <foo.h>. This cannot be considered
  # valid from the point of view of the project's build context because
  # the inclusion of <otherfoo.h> might be conditional on a #define that
  # the program defines but that we do not define in our test.
  #
  # The ./configure test fails to detect if we cannot find config.cache,
  # if configure.in does not contain a test for the desired header file,
  # if the header file's name cannot be successfully converted into a
  # configure variable name, or if the algorithm used by AC_CHECK_HEADER
  # succombs to a problem like that mentioned above.

  # Test for compile test first
  @trypats = ("<fn><dp>no such file",
              "<fn><dp>file not found",
              "no such file",
              "file not found");
  $prog = "int main(int argc, char *argv[])\n"
        . "{\n"
        . "  return(0);\n"
        . "}\n";
  $tfn = "fubarbazqux.h";
  &try_link(1, 0, $prog, $tfn);
  $g_tth_l = 0;
  open(THIN, "cat hsrun-tout hsrun-terr |");
  while ($l = <THIN>) {
    &prnt("$O: tth: $l");
    chop $l;
    for($i=0; ($g_tth_l == 0) && ($i<=$#trypats); $i++) {
      $g_thpat = &map_pat($trypats[$i], $tfn);
      if ($l =~ m/$g_thpat/i) {
        $g_thpat = $trypats[$i];
        $g_tth_l = 1;
        &prnt("$O: tth: matched by /$g_thpat/i\n");
      }
    }
  }
  close THIN;

  # If we don't have the ability to detect missing headers, then we want it
  # to act like the header is there, which means we want to not match anything
  # so as to think there was no "missing file" error. Thus, we set g_tthpat to
  # a pattern that won't match anything.
  if ($g_tth_l == 0) {
    $g_thpat = "nomatch_fubarbazqux";
  }

  # Now try to do the config.cache-based test.

  $g_tth_c = 0;
  if ($ccpath = "../config.cache", -e $ccpath) {
    # found it
  } elsif ($ccpath = "../../config.cache", -e $ccpath) {
    # found it
  } else {
    $ccpath = "";
  }
  if ($ccpath ne "") {
    &prnt("$O: tth: found config.cache: ");
    $g_tth_c = 2;
    open(CC_IN, $ccpath);
    while($l = <CC_IN>) {
      if($l =~ m/^ac_cv_header_([a-z_0-9]+_h)\=\$\{ac_cv_header_([a-z_0-9]+_h)\=([a-z]+)\}/) {
        $k1 = $1;  $k2 = $2; $v = $3;
        if ($v =~ m/yes/i) {
          $v = 1;
        } else {
          $v = 0;
        }
        if ($k1 eq $k2) {
          &prnt("$k1 ");
          $cc_exists{$k1} = $v;
        }
      }
    }
    close CC_IN;
    &prnt("\n");
  }

  $g_tth = $g_tth_l | $g_tth_c;

  return $g_tth;
}

# Return vector containing all level L headers.
sub lev_vec
{
  local($l) = @_;
  local($i, @v1, $j);

  @v1 = (); $j = 0;
  for($i=0; $i<$nheaders; $i++) {
    if ($e_level{$ent[$i]} == $l) {
      $v1[$j] = $ent[$i];
      $j++;
    }
  }

  return (@v1);
}

# Return vector containing all level L headers of level <= L (in level order).
sub lowers_vec
{
  local($l) = @_;
  local($i, @lowers);

  @lowers = ();
  for($i=1; $i<$level; $i++) {
    @lowers = (@lowers, &lev_vec($i));
  }

  return (@lowers);
}

# Return vector containing the include files that a given header depends on
sub needs_vec
{
  local($ent) = @_;
  local(@v);

  @v = split / +/, $e_needs_list{$ent};
  return(@v);
}

# Return vector containing all include files needed by all headers of level L
sub lev_needs_vec
{
  local($l) = @_;
  local($i, @v);

  @v = ();
  for($i=1; $i<$nheaders; $i++) {
    if ($e_level{$ent[$i]} == $l) {
      @v = (@v, &needs_vec($ent[$i]));
    }
  }

  return (@v);
}

# Reverse elements in a vector
sub reverse
{
  local(@v) = @_;
  local($i, $l, $t);

  $l = $#v;
  for($i=0; $i+$i < $l; $i++) {
    $t = $v[$i];
    $v[$i] = $v[$l-$i];
    $v[$l-$i] = $t;
  }
  return(@v);
}

# Given a list of headers that contains at least one clash tuple (or
# warning tuple), reduce3 reduces the list as far as it can until it
# is down to a single pair, or until the clash mysteriously vanishes
# (which means there is a clash triplet). It does this by repeatedly
# removing one third of the items, based on the idea that if the list
# contains two items that clash, and you split it into three groups,
# one of the groups must not contain either of the two clashing items.
sub reduce3
{
  local($warnings_count, $level, $ename, $need_nvv, @v) = @_;
  local($going, $n, $i);
  local(@s1, @s2, @s3); # subsets
  local(@nvv);

  if ($need_nvv) {
    @nvv = &lev_needs_vec($level);
  } else {
    @nvv = ();
  }

  &prnt("$O: Reducing set to find $ename-pair: ");

  while (1) {
    $n = $#v;
    &prnt(($n+1) . " ");
    if ($n < 2) {
      &prnt("@v\n");
      return @v;
    }

    @s1 = (); @s2 = (); @s3 = ();
    for($i=0; $i<=$n; $i++) {
      if ($i % 3 == 0) {
        @s1 = (@s1, $v[$i]);
      } elsif ($i % 3 == 1) {
        @s2 = (@s2, $v[$i]);
      } else {
        @s3 = (@s3, $v[$i]);
      }
    }

    if (&try_headers($warnings_count, @nvv, @s1, @s2) == 0) {
      @v = (@s1, @s2);
    } elsif (&try_headers($warnings_count, @nvv, @s1, @s3) == 0) {
      @v = (@s1, @s3);
    } elsif (&try_headers($warnings_count, @nvv, @s2, @s3) == 0) {
      @v = (@s2, @s3);
    } else {
      # They all worked! That means there's a three-way mutual clash.
      &prnt(" 3+tuples @v\n");
      return(@v);
    }
  }
}

# Return a vector with one item nulled out
sub except
{
  local($e, @v) = @_;
  local($i, $n);

  $n = $#v;
  for($i=0; $i<=$n; $i++) {
    if($v[$i] eq $e) {
      $v[$i] = "";
    }
  }
  return @v;
}

sub clash_cat
{
  local($inwarn, $level, $ename, $ENAME, $need_nvv, @v) = @_;
  local(@vr);
  local(@nvv);
  local($forcefull);

  if ($need_nvv) {
    @nvv = &lev_needs_vec($level);
  } else {
    @nvv = ();
  }

  $forcefull = 0;

  @vr = &reduce3($inwarn, $level, $ename, $need_nvv, @v);
  if ($#vr == 1) {
    # Check to make sure this is the only tuple.
    if (  (&try_headers($inwarn, @nvv, &except($vr[0], @v)))
       && (&try_headers($inwarn, @nvv, &except($vr[1], @v))) )
    {
      # Both worked, that means this is the only clash; catalog it
      $ctag = "$vr[0],$vr[1]";
      if ($inwarn) {
        $warnings{$ctag} = 1;
      } else {
        $clashes{$ctag} = 1;
      }

      # also check it the other way around
      if(&try_headers($inwarn, @nvv, $vr[1], $vr[0]) == 0) {
        $ctag = "$vr[1],$vr[0]";
        if ($inwarn) {
          $warnings{$ctag} = 1;
        } else {
          $clashes{$ctag} = 1;
        }
      }
    } else {
      &prnt("$O: There are multiple $ename-pairs; full search forced.\n");
      $forcefull = 1;
    }
  } else {
    &prnt("$O: Vector was irreducible over $ename; full search forced.\n");
    $forcefull = 1;
  }

  if ($forcefull) {
    &prnt("$O: Catalogging $ename tuples: ");
    for($h1=0; $h1<$nheaders; $h1++) {
      for($h2=0; $h2<$nheaders; $h2++) {
        # At least one of the entities must be of our level, otherwise
        # the pair would have been tested before (or cannot be tested yet
        # because one is of as-yet-unknown level) We also don't bother trying
        # any lower-level headers that have been banned to a negative level.
        if ( ($h1 != $h2)
          && ($e_level{$ent[$h1]} > 0) && ($e_level{$ent[$h2]} > 0)
          && (   ($e_level{$ent[$h1]} == $level)
              || ($e_level{$ent[$h2]} == $level))
        ) {
          @l1 = &needs_vec($ent[$h1]);
          @l2 = &needs_vec($ent[$h2]);
          $ctag = "$ent[$h1],$ent[$h2]";
          &prnt("$ctag:");
          if (&try_headers($inwarn, @l1, @l2, $ent[$h1], $ent[$h2])) {
            &prnt("ok ");
          } else {
            &prnt("$ENAME ");
            if ($inwarn) {
              $warnings{$ctag} = 1;
            } else {
              $clashes{$ctag} = 1;
            }
          }
        }
      }
    }
    &prnt("\n");
  }
}

# banish is given a list of entities that is known to contain some clashes
# (as given by %clashes). It "banishes" entities one at a time until
# there are no active clash tuples left. A tuple is "inactive" if one or
# both of its members is banished. An entity is "banished" if its level
# has been set to a negative value. Level 1 headers get banished to level -1,
# level 2 headers are banished to level -2, and so on.
sub banish
{
  local(@v) = @_;
  local($k, $e1, $e2, $i, $n);
  local(%pop);
  local($maxe, $maxp);

  $n = $#v;
  while(1) {
    # Zero out the populations
    for($i=0; $i<=$n; $i++) {
      $pop{$v[$i]} = 0;
    }
    foreach $k (keys %clashes) {
      $k =~ m/^(.+)\,(.+)$/; $e1 = $1; $e2 = $2;
      $pop{$e1} = 0; $pop{$e2} = 0;
    }
    # Find the most popular clasher that is still a member of an active
    # tuple.
    foreach $k (keys %clashes) {
      $k =~ m/^(.+)\,(.+)$/; $e1 = $1; $e2 = $2;
      if (($e_level{$e1} > 0) && ($e_level{$e2} > 0)) {
        $pop{$e1} += 1; $pop{$e2} += 1;
      }
    }
    $maxp = 0;
    for($i=0; $i<=$n; $i++) {
      if ($pop{$v[$i]} > $maxp) {
        $maxp = $pop{$v[$i]};
        $maxe = $v[$i];
      }
    }
    # If the max population is 0, that means there are no active tuples left
    if ($maxp == 0) {
      # We're done!
      return;
    }
    # This is the one we want to banish
    $e_level{$maxe} = 0 - $e_level{$maxe};
    &prnt("$O: set $maxe to level $e_level{$maxe} to avoid more errors\n");
  }
}

sub level_info
{
  local($level, $n_ln, @lowers) = @_;
  local($inwarn, $going);
  local(@mds, @l1, @l2);
  local(@lw_rev);

  if ($level > 1) {
    for($i=0; $i<$nheaders; $i++) {
      if ($e_level{$ent[$i]} == $level) {
        &prnt("$O: Trying to find minimal dependency set for $ent[$i]: ");
        @mds = @lowers;
        for($j=0; $j<=$#mds; $j++) {
          # save this entity, blank it out, and compile
          $t_ent = $mds[$j];
          $mds[$j] = "";
          &prnt("$t_ent-");
          $ctag = "$ent[$i],$t_ent";
          if(&try_headers(0, @mds, $ent[$i])) {
            # It worked, so we can leave this one out
            &prnt("0 ");
            $depend{$ctag} = 0;
          } else {
            # Error, must put this one back in
            &prnt("1 ");
            $depend{$ctag} = 1;
            $mds[$j] = $t_ent;
          }
        }
        &prnt("\n");
        &prnt("$O: $ent[$i] depends on: ");
        $e_mds = "";
        for($j=0; $j<=$#mds; $j++) {
          if ($mds[$j] ne "") {
            $e_mds .= "$mds[$j] ";
          }
        }
        &prnt("$e_mds\n");
        $e_needs_list{$ent[$i]} = $e_mds;
      }
    }
  }

  if(&test_gcc()) {
    &prnt("$O: Testing for warnings: ");
    for($i=0; $i<$nheaders; $i++) {
      if ($e_level{$ent[$i]} == $level) {
        &prnt($ent[$i]);
        if (&try_headers(1, &needs_vec($ent[$i]), $ent[$i])) {
          # It worked
          &prnt(":0 ");
          $e_selfwarn{$ent[$i]} = 0;
        } else {
          # Error: this entity got warnings
          $e_selfwarn{$ent[$i]} = 1;
          &prnt(":1 ");
        }
      }
    }
    &prnt("\n");
  }

  $ooflag = 0;
  &prnt("$O: Testing idempotency (level $level): ");
  for($i=0; $i<$nheaders; $i++) {
    if ($e_level{$ent[$i]} == $level) {
      &prnt($ent[$i]);
      if (&try_headers(1, &needs_vec($ent[$i]), $ent[$i], $ent[$i])) {
        # Worked even with warnings turned on
        &prnt(":0 ");
        $e_onceonly{$ent[$i]} = 0;
      } elsif (&try_headers(0, &needs_vec($ent[$i]), $ent[$i], $ent[$i])) {
        # Failed on warnings but worked on errors
        &prnt(":1 ");
        $e_onceonly{$ent[$i]} = 1;
      } else {
        # Error: this entity is once-only
        $e_onceonly{$ent[$i]} = 2;
        &prnt(":2 ");
        $ooflag = 1;
      }
    }
  }
  &prnt("\n");

  @nvv = &lev_needs_vec($level);

  for($inwarn=0; $inwarn<$lvlclean; $inwarn++) {
    $ename = ($inwarn ? "warning" : "clash");
    $ENAME = ($inwarn ? "WARNING" : "CLASH");

    $clash[$inwarn] = 0;
    @v1 = &lev_vec($level);
    &prnt("$O: Checking for $ename 1-N (level $level): ");
    if (&try_headers($inwarn, @lowers, @v1)) {
      &prnt("OK.\n");
    } else {
      &prnt("$ENAME\n");
      $clash[$inwarn] = 1;

      # There are clashes, need to create matrix of what clashes with what
      # and in what order.
      &clash_cat($inwarn, $level, $ename, $ENAME, 0, @lowers, @v1);

      # Now eliminate individual headers until there are no clash tuples
      # left; this is essential for finding headers of level L+1
      if ($inwarn == 0) {
        &banish(&lev_vec($level));
      }
    }

    &prnt("$O: Checking for $ename N-1 (level $level)");
    @v1 = &reverse(&lev_vec($level));
    if ($no_oo_yet) {
      &prnt(" (with trailing lowers)");
      @lw_rev = &reverse(@lowers);
      @v1 = (@v1, @lw_rev);
    }
    $t = &try_headers($inwarn, @nvv, @v1);
    if ($t) {
      &prnt(": OK.\n");
    } else {
      &prnt(": $ENAME\n");
      $clash[$inwarn] = 1;
      # Same handling as above, except that clash_cat has to interpolate
      # @nvv
      &clash_cat($inwarn, $level, $ename, $ENAME, 1, @v1);
      if ($inwarn == 0) {
        &banish(&lev_vec($level));
      }
    }
  }
  # If we have mutual warnings, it kinda screws up the 1-N and N-1 tests
  # for warnings, so we won't bother doing those next level. This problem
  # doesn't exist for error scanning because of banishing.
  if ($clash[1]) {
    $lvlclean = 1;
    &prnt("$O: warning tuple scanning disabled for levels above $level.\n");
  }

  if ($ooflag) {
    # Tell higher levels they can't do the quick full N-1 clash test
    $no_oo_yet = 0;
  }
}

sub level_scan
{
  local($level) = @_;
  local(@lowers);
  local($anyleft);

  $anyleft = 0;
  for($i=0; $i<$nheaders; $i++) {
    if($e_level{$ent[$i]} == 0) {
      $anyleft = 1;
    }
  }
  if ($anyleft == 0) {
    &prnt("$O: All header files accounted for!\n");
    $going = 0;
    return;
  }

  &prnt("$O: Scanning for level $level ");
  &prnt("(stand-alone) ") if ($level == 1);
  &prnt("headers: ");

  if ($level > 1) {
    @lowers = &lowers_vec($level - 1);
  } else {
    @lowers = ();
  }

  $n_ln = 0;
  $anyleft = 0;
  for($i=0; $i<$nheaders; $i++) {
    if($e_level{$ent[$i]} == 0) {
      $anyleft = 1;
      # level for this entity is as yet unknown.
      if (&try_headers(0, @lowers, $ent[$i])) {
        &prnt($ent[$i] . " ");
        $e_level{$ent[$i]} = $level;
        $n_ln++;
      }
    }
  }
  &prnt(" ... $n_ln found.\n");
  if ($n_ln == 0) {
    &prnt("$O: None of the remaining headers is level $level or higher.\n");
    $going = 0;
  } else {
    &level_info($level, $n_ln, @lowers);
  }
}

sub cleanup
{
  if (-e "hsrun-temp.c") {
    unlink("hsrun-temp.c");
  }
  if (-e "hsrun-temp.o") {
    unlink("hsrun-temp.o");
  }
  if (-e "hsrun-temp") {
    unlink("hsrun-temp");
  }
  if (-e "hsrun-terr") {
    unlink("hsrun-terr");
  }
  if (-e "hsrun-tout") {
    unlink("hsrun-tout");
  }
}

sub env_info
{
  local($issue, $t, $us, $ur);

  $uname = `uname -a`; chop $uname;
  &prnt("$O: uname -a: $uname\n");

  $us = `uname -s`; chop($us);
  $ur = `uname -r`; chop($ur);

  $distro = "Unknown";
  $tersename = "$us.$ur";
  if ($us =~ m/linux/i) {
    $issue = `cat /etc/issue`;
    if ($issue =~ m/Red Hat Linux release ([0-9]\.[0-9])/) {
      $distro = "Red Hat " . $1;
      $tersename = "rh.$1";
    } elsif ($issue =~ m/Linux Mandrake release ([0-9]\.[0-9])/) {
      $distro = "Mandrake " . $1;
      $tersename = "md.$1";
    } elsif (-e "/etc/slackware-version") {
      $t = `cat /etc/slackware-version | head -1`; chop $t;
      if ($t =~ m/^([0-9]\.[0-9])/) {
        $distro = "Slackware " . $1;
        $tersename = "sw.$1";
      }
    }
    &prnt("$O: Linux distribution: $distro\n");
  } else {
    $distro = "N/A (not Linux)";
  }

  $date = `date`; chop $date;
  &prnt("$O: date: $date\n");

}

$| = 1;

$O = $0; if ($O =~ m|/([^/]+)$|) { $O = $1; }
print "$O started.\n";

$outfile = "headerstats.out";
$logfile = "headerstats.log";
$tname = "hsrun-temp";
$tname_c = "$tname.c";
$tname_o = "$tname.o";

$counter = 0; $elapsed = time;

$rcfile = "rc.headerstats";

if (-e $rcfile) {
  # all set
} elsif (-e "src/$rcfile") {
  chdir("src");
} else {
  print "$O: Cannot locate rc.headerstats\n";
  exit 1;
}

open(LOG, "> $logfile");
print LOG "$O started.\n";

&env_info();

&prnt("$O: reading makefile variables.\n");
open(RCFILE, $rcfile);
while($l = <RCFILE>) {
  chop $l;
  if ($l =~ m/^([A-Z_0-9]+) +\= +([^ ].*)$/) {
    $label = $1; $value = $2;
    $vars{$label} = &quote1($value);
  } else {
    &prnt("$O Cannot parse setting $l\n");
  }
}
close RCFILE;

&prnt("$O: testing to see if compiler is gcc: ");
&prnt(&test_gcc() ? "yes\n" : "no\n");

&prnt("$O: testing ability to detect warnings: ");
&prnt(&test_testwarn() ? "yes\n" : "no\n");

&prnt("$O: testing ability to detect existence of header files.\n");
&test_testhave();
&prnt("$O:         ability to detect existence of header files: $g_tth\n");

if (-e $tname_c) {
  unlink($tname_c);
}

&prnt("$O: compiling list of entities: ");
$nheaders = 0;
open(FIND, "find . -print |");
while($fn = <FIND>) {
  chop $fn;
  if (($fn =~ m/\.c$/)
    ||($fn =~ m/\.h$/)
    ||($fn =~ m/\.cc$/) )
  {
    # source file
    open(IN, $fn);
    while($l = <IN>) {
      chop $l;
      if ($l =~ m/^\#[ \t]*include *\<([^\>]+)\>/) {
        $entity = $1;
        if ($enum{$entity} eq "") {
          $ent[$nheaders] = $entity;
          $enum{$entity} = $nheaders;
          &prnt("$entity ");
          $nheaders++;
        }
      }
    }
    close IN;
  }
}
close FIND;
&prnt("... $nheaders found.\n");

&prnt("$O: Checking which ones exist: ");
for($i=0; $i<$nheaders; $i++) {
  &prnt($ent[$i] . ":");
  $e_exists{$ent[$i]} = &test_have($ent[$i]);
  &prnt($e_exists{$ent[$i]} . " ");
}
&prnt("\n");

# Remove the nonexistent ones by rebuilding the array. We also take advantage
# of this opportunity to sort the headers so that hsruns in the same project
# on different target systems will check their headers in the same order.
@ent = ();
$nheaders = 0;
foreach $entity (sort (keys %e_exists)) {
  if ($e_exists{$entity}) {
    $ent[$nheaders] = $entity;
    $enum{$entity} = $nheaders;
    $nheaders++;
  } else {
    $enum{$entity} = -1;
  }
}
&prnt("$O: There are $nheaders headers to test.\n");

$level = 0;
$lvlclean = 2; $no_oo_yet = 1;
$going = 1;
while($going) {
  $level++;
  &level_scan($level);
}

$eh = "";
for($i=0; $i<$nheaders; $i++) {
  if ($e_level{$ent[$i]} == 0) {
    $eh = $eh . $ent[$i] . " ";
  }
}
if ($eh ne "") {
  &prnt("$O: Couldn't figure out how to make these headers work: $eh\n");
}

if ($g_tth_c == 0) {
  &prnt("$O: Suggest adding a path test rule for confg.cache.\n");
} else {
  if ($g_tth_l == 0) {
    $eh = "";
    foreach $k (sort (keys %e_exists)) {
      $cvn = $k;
      $cvn =~ tr/A-Z./a-z_/;
      if ($cc_exists{$cvn} eq "") {
        $eh = $eh . "$k ";
      }
    }
    if ($eh ne "") {
      &prnt("$O: I suggest adding AC_CHECK_HEADER tests for: $eh\n");
    }
  }
}
if ($g_tth_l == 0) {
  &prnt("$O: I suggest adding a missing-file regexp for this compiler\n");
}

$elapsed = time - $elapsed;
&prnt("$O: Performed $counter compiles and links in $elapsed seconds.\n");

&cleanup();

&prnt("$O: writing $outfile... ");
open(OUT, "> $outfile");
print OUT "This file was generated by hsrun\n";
print OUT "uname -a: $uname\n";
print OUT "Linux distribution: $distro\n";
print OUT "date: $date\n";
print OUT sprintf("%25s e  L wa oo dependency-list--------------------\n", "----------header-files");
foreach $entity (sort (keys %e_exists)) {
  print OUT sprintf("%25s", $entity);
  print OUT (" " . $e_exists{$entity});
  if ($e_exists{$entity}) {
    $n = $enum{$entity};
    print OUT sprintf(" %2d", $e_level{$entity});
    print OUT ($e_selfwarn{$entity} ? " wa" : "   ");
    print OUT ($e_onceonly{$entity} ? " oo" : "   ");
    print OUT (" " . $e_needs_list{$entity});
  }
  print OUT "\n";
}
print OUT "clash tuples:\n"; $l = "  none\n";
foreach $ctag (sort (keys %clashes)) {
  print OUT "  $ctag\n"; $l = "";
}
print OUT $l;
print OUT "warning tuples:\n"; $l = "  none\n";
foreach $ctag (sort (keys %warnings)) {
  print OUT "  $ctag\n"; $l = "";
}
print OUT $l;
print OUT "\n";
print OUT $legend;
print OUT "\n";
print OUT $terminology;
close OUT;
&prnt("done.\n");
close LOG;

if (-d "../hs-archive") {
  system("cp -f $outfile ../hs-archive/hs.out.$tersename");
  system("cp -f $logfile ../hs-archive/hs.log.$tersename");
}
