#!/usr/bin/perl -w
# /Trilinos/commonTools/test/utilities/runconfigure

################################################################################
# The Trilinos Project - runconfigure
# 
# Mike Phenow, Jim Willenbring
#
# This is a utility for configuring Trilinos.  It requires you to provide a
# complete invoke-configure file.  It is of limited value by itself.  It is
# best used in conjunction with runmake, as in the utility runbuild.
#
# - Check contents of report.
# - Improve comments.
# - Should dependencies be moved into packages like test definitions?
# - Do we need a dependencies file (or the same one) to specify which package
#   directories to blow away?
# - Output package names as they configure successfully.
#
################################################################################

use strict;

# Variable Declarations ========================================================

# Command line arguments:

my $trilinosDir;        # Trilinos directory        (required argument) 
my $buildDir;           # build directory           (required argument) 
my $invokeConfigure;    # complete invoke-configure (required argument)
my $recover;            # recover mode?
my $resume;             # resume mode? (use existing invoke-configure)
my $newMakeTarget;      # new make target? (use last invoke-configure)
my $makeBrokenPackage;  # package the failed to make
my $makeBrokenSubdir;   # package subdirectory in which make failed

my $outputDir;          # output directory          (default: .)
my $verbosity;          # verbosity level           (default: 1)
my $logVerbosity;       # log file verbosity level  (default: 0)

my $resultsDir;         # absolute path to results directory
my %dependencies;       # package dependencies HoHoA

my $runStartTime;
my $runStartTimeForFilename;

# Constants
my $v0 = "0";           # quiet
my $v1 = "1";           # normal verbosity
my $v2 = "2";           # level 2 verbosity
my $v3 = "4";           # level 3 verbosity
        
################################################################################
# Execution ####################################################################
################################################################################

getArgs();
init();
parseDependencies();

my $exitStatus = run();
cleanUp();
exit $exitStatus;

################################################################################
# Subroutines ##################################################################
################################################################################

    ############################################################################
    # getArgs()
    #
    # Parse command line arguments.
    #
    #   - args:     NONE
    #
    #   - returns:  NONE
    #

    sub getArgs {
        
        # Argument variables that don't need to be global.
        my $quiet;
        my $help;

        # Gather command line arguments.
        use Getopt::Long;
        GetOptions( "trilinos-dir=s" => \$trilinosDir,
                    "build-dir=s" => \$buildDir,
                    "invoke-configure=s" => \$invokeConfigure,
                    "recover" => \$recover,
                    "resume" => \$resume,
                    "new-make-target" => \$newMakeTarget,
                    "make-broken-package=s" => \$makeBrokenPackage,
                    "make-broken-subdir=s" => \$makeBrokenSubdir,
                    "output-dir=s" => \$outputDir,
                    "verbosity=i" => \$verbosity,
                    "log-verbosity=i" => \$logVerbosity,
                    "quiet" => \$quiet,
                    "help" => \$help );
        
        # Print help and exit.
        if ($help) { 
            printHelp();
            exit;
        }
        
        # Enforce and/or prepare arguments.
        
        # check for existance of trilinos-dir argument and actual directory
        if (!$trilinosDir) {
            die "trilinos-dir value required, see --help for more information\n"; 
        } else {
            if (!stat($trilinosDir)) {
                die "cannot stat trilinos-dir: $trilinosDir\n";
            }
        }
        
        # Check for existance of build-dir argument and actual directory.
        # If it isn't given as an absolute path, prepend the Trilinos path.
        # Blow it away and create it.
        if (!$buildDir) {
            die "build-dir value required, see --help for more information\n"; 
        } else {
            if ($buildDir !~ m/^\//) {
                $buildDir = "$trilinosDir/$buildDir";
            }
            if (!$makeBrokenPackage && !$resume && !$newMakeTarget) {
                system ("rm -rf $buildDir");
                system("mkdir $buildDir") == 0 or die "cannot create build-dir: $buildDir\n";
            }
        }
        
        # Prevent problems with uninitialized value
        if (!$recover) {
            $recover = "";
        }
        
        # Prevent problems with uninitialized value
        if (!$newMakeTarget) {
            $newMakeTarget = "";
        }
        
        # Prevent problems with uninitialized value
        if (!$resume) {
            $resume = "";
        }
        
        # Check for existance of invoke-configure argument and actual file
        if (!$invokeConfigure && !$resume && !$newMakeTarget && !$makeBrokenPackage) {
            die "invoke-configure, resume, new-make-target, or make-broken-package required, see --help for more information\n"; 
        } elsif ($invokeConfigure && $resume) {
            die "invoke-configure and resume options mutually exclusive\n";
        } elsif ($invokeConfigure && $newMakeTarget) {
            die "invoke-configure and new-make-target options mutually exclusive\n";
        } elsif ($resume || $newMakeTarget || $makeBrokenPackage) {
            $invokeConfigure = "";
        } else {
            if ($invokeConfigure !~ m/^\//) {
                $invokeConfigure = "$trilinosDir/commonTools/test/utilities/$invokeConfigure";
            }
            if (!stat($invokeConfigure)) {
                die "cannot stat invoke-configure: $invokeConfigure\n";
            } 
        }
        
        # Check for existance of output directory, use it to create the
        # complete path for the results directory, and create the results
        # directory.
        if (!$outputDir) {
            $outputDir = "";
            $resultsDir = "$trilinosDir/commonTools/test/utilities/results"; 
        } else {
            if ($outputDir =~ m/^\//) {
                $resultsDir = $outputDir;
            } else {
                $resultsDir = "$trilinosDir/commonTools/test/utilities/$outputDir";
            }
        }
        if (!stat($resultsDir)) {
            system("mkdir $resultsDir") == 0 or die "cannot create $resultsDir, died";
        }
        
        # Set verbosity level to corresponding constant.  0, 1, 2, and 3 are
        # used for the levels the user may specify, but they are stored as 0,
        # 1, 2, and 4 so they can be combined and processed with bitwise
        # operations.
        if ($verbosity) {
            if      ($verbosity == 0) { $verbosity = $v0; }
            elsif   ($verbosity == 1) { $verbosity = $v1; }
            elsif   ($verbosity == 2) { $verbosity = $v2; }
            elsif   ($verbosity == 3) { $verbosity = $v3; }
        } else {
            $verbosity = $v1; 
        }
        
        # Set log verbosity level to corresponding constant.  Numbering scheme
        # is the same for the log verbosity as it is for the standard verbosity.
        # There is not distinct output and log output, the if a log verbosity
        # is given, then that level of output is written to a file instead of
        # to standard out.  
        if ($logVerbosity) {
            if      ($logVerbosity == 0) { $logVerbosity = $v0; }
            elsif   ($logVerbosity == 1) { $logVerbosity = $v1; }
            elsif   ($logVerbosity == 2) { $logVerbosity = $v2; }
            elsif   ($logVerbosity == 3) { $logVerbosity = $v3; }        
        } else {
            $logVerbosity = $v1; 
        }
        
        # Set quiet mode--same as passing --verbosity=0.
        if ($quiet) {
            $verbosity = $v0; 
        }
        
    } # getArgs()

    ############################################################################
    # init()
    #
    # Prepares variables.
    #
    #   - args:     NONE
    #
    #   - returns:  NONE
    #

    sub init {
    
        # Capture and format make start time.
        (my $se, my $mn, my $hr, my $da, my $mo, my $yr) = (localtime)[0..5];
        $yr = sprintf("%02d", $yr % 100);
        $mo = sprintf("%02d", $mo+1);
        $da = sprintf("%02d", $da);
        $hr = sprintf("%02d", $hr);
        $mn = sprintf("%02d", $mn);
        $se = sprintf("%02d", $se);
        $runStartTime = $yr."-".$mo."-".$da." ".$hr.":".$mn.":".$se;
        $runStartTimeForFilename = $yr."-".$mo."-".$da."_".$hr.".".$mn.".".$se;
        
        # Print list of variables for debugging.
        my $message = "";
        $message .= "init():\n";
        $message .= "  \$trilinosDir = $trilinosDir\n";
        $message .= "  \$buildDir = $buildDir\n";     
        $message .= "  \$invokeConfigure = $invokeConfigure\n";        
        $message .= "  \$makeBrokenPackage = ".($makeBrokenPackage?$makeBrokenPackage:"")."\n";      
        $message .= "  \$makeBrokenSubdir = ".($makeBrokenSubdir?$makeBrokenSubdir:"")."\n"; 
        $message .= "  \$recover = $recover\n";
        $message .= "  \$resume = $resume\n";
        $message .= "  \$newMakeTarget = $newMakeTarget\n";
        $message .= "  \$outputDir = $outputDir\n";
        $message .= "  \$verbosity = $verbosity\n";
        $message .= "  \$logVerbosity = $logVerbosity\n";
        $message .= "  \$resultsDir = $resultsDir\n";
        $message .= "  \n";
        printMessage($message, $v3);
        
    } # init()
    
    ############################################################################
    # run()
    #
    # Moves into build-dir/ and configures.
    #
    #   - args:     NONE
    #
    #   - returns:  NONE
    #

    sub run {
        
        # Copy invoke-configure
        if (!$resume && !$newMakeTarget) {
            if (!$makeBrokenPackage) {
                system("cp $invokeConfigure $buildDir/invoke-configure") == 0
                    or die "cannot copy invoke-configure, died";
            } else {
                # Move aside current, broken invoke-configure.  Serialize
                # broken invoke-configures so that all are preserved.
                my $icBrokenNumber = "00";
                while (stat "$buildDir/invoke-configure-broken-$icBrokenNumber") {
                    $icBrokenNumber++;
                }
                system("cp $buildDir/invoke-configure $buildDir/invoke-configure-broken-$icBrokenNumber") == 0
                    or die "can't copy invoke-configure, died";
                
                if ($makeBrokenSubdir ne "tests" && $makeBrokenSubdir ne "examples") {
                    my $packageDir  = "$buildDir/packages/".getDirName($makeBrokenPackage);        
                    system("rm -rf $packageDir");
                }
                my $result = cleanInvokeConfigure($makeBrokenPackage, $makeBrokenSubdir);
                if ($result == 86) { return 86; }
            }
        }
        
        chdir $buildDir or die "cannot chdir to $buildDir, died";
            
        # grab contents of invoke-configure for reporting purposes.
        my $invokeConfigureString = "";              
        open (INVOKE_CONFIGURE, "<invoke-configure");
        undef $/;                                       # undefine input record separator
        $invokeConfigureString=<INVOKE_CONFIGURE>;      # copy entire file
        $/ = "\n";                                      # restore it to default newline
        close INVOKE_CONFIGURE;
            
        my $configurePassed = 0;
        my $eightySix;
        my $configLog = "";
        
        # Attempt to configure.  If --recover flag was passed, we will
        # continue to attempt to recover and reconfigure until it passes.
        while (!$configurePassed) {
    
            # Capture and format make start time.
            (my $se, my $mn, my $hr, my $da, my $mo, my $yr) = (localtime)[0..5];
            $yr = sprintf("%02d", $yr % 100);
            $mo = sprintf("%02d", $mo+1);
            $da = sprintf("%02d", $da);
            $hr = sprintf("%02d", $hr);
            $mn = sprintf("%02d", $mn);
            $se = sprintf("%02d", $se);
            $runStartTime = $yr."-".$mo."-".$da." ".$hr.":".$mn.":".$se;
            $runStartTimeForFilename = $yr."-".$mo."-".$da."_".$hr.".".$mn.".".$se;
            
            # Print progress message.
            my $message = "Configuring Trilinos...";
            $message = sprintf("%-25s", $message);
            printMessage($message, $v1+$v2+$v3);
            
            # Capture start time.
            my $startSeconds = time();
        
            # Run configure.  
            my $configureOutput = `./invoke-configure 2>&1`;
            my $configureExitStatus = $?;
        
            # Capture and format make stop time.
            ($se, $mn, $hr, $da, $mo, $yr) = (localtime)[0..5];
            $yr = sprintf("%02d", $yr % 100);
            $mo = sprintf("%02d", $mo+1);
            $da = sprintf("%02d", $da);
            $hr = sprintf("%02d", $hr);
            $mn = sprintf("%02d", $mn);
            $se = sprintf("%02d", $se);
            my $configureStopTime = $yr."-".$mo."-".$da." ".$hr.":".$mn.":".$se;
            my $stopSeconds = time();            
            my $runSeconds = $stopSeconds - $startSeconds;
            
            # Broken or not?
            my $brokenPackage = "NONE";
            my $configuredPackages = detectConfiguredPackages($configureOutput);
            if ($configureExitStatus) {
                
                # Move aside current, broken invoke-configure.  Serialize
                # broken invoke-configures so that all are preserved.
                my $icBrokenNumber = "00";
                while (stat 
                    "$buildDir/invoke-configure-broken-$icBrokenNumber") {
                    $icBrokenNumber++;
                }
                system("cp $buildDir/invoke-configure $buildDir/invoke-configure-broken-$icBrokenNumber") == 0
                    or die "can't copy invoke-configure, died";
            
                # grab contents of invoke-configure for reporting purposes.
                my $invokeConfigureString = "";              
                open (INVOKE_CONFIGURE, "<invoke-configure");
                undef $/;                   # undefine input record separator
                $invokeConfigureString=<INVOKE_CONFIGURE>;  # copy entire file
                $/ = "\n";                  # restore it to default newline
                close INVOKE_CONFIGURE;
            
                # Detect broken package, remove broken package directory, and
                # remove appropriate configure flags.
                $brokenPackage = detectBrokenPackage($configureOutput);                                
                # grab contents of broken package's config.log
                if ($brokenPackage ne "NONE" && $brokenPackage ne "UNKNOWN") {                
                    open (CONFIG_LOG, "<$buildDir/packages/$brokenPackage/config.log");
                    undef $/;                   # undefine input record separator
                    $configLog=<CONFIG_LOG>;    # copy entire file
                    $/ = "\n";                  # restore it to default newline
                    close CONFIG_LOG;
                }
                
            
                my $packageDir  = "$buildDir/packages/".getDirName($brokenPackage);        
                system("rm -rf $packageDir");
                my $result = cleanInvokeConfigure($brokenPackage);
                if ($result == 86) { $eightySix = 1; }
                
                $message = sprintf("%-30s", " broken: $brokenPackage ");
                printMessage($message, $v1+$v2+$v3);
                $message = "! FAILED ";
                printMessage($message, $v1+$v2+$v3);
            } else {
                $message = sprintf("%-30s", "");
                printMessage($message, $v1+$v2+$v3);
                $message = "  passed ";
                printMessage($message, $v1+$v2+$v3);
                $configurePassed = 1;
            }
            
            # Trim $configureOutput.
            $configureOutput = substr ($configureOutput, -1000000);
                                
            # Finish printing pass/fail message.
            $message = ($runSeconds==0?"<1":$runSeconds);
            $message = sprintf("%6s", $message);
            printMessage($message, $v1+$v2+$v3);
            $message = " second".($runSeconds>1?"s":"")."\n";
            printMessage($message, $v1+$v2+$v3);
                                                
        # Grab general information values for inclusion in machine info file. 
        my $hostName = "";          my $dnsName = "";           my $ipAddress = "";     
        my $operatingSystem = "";   my $kernelName = "";        my $kernelRelease = "";
        my $kernelVersion = "";     my $processor = "";         my $machineHardware = "";
        my $hardwarePlatform = "";  my $badCmd = 0;          
        $badCmd = system('hostname -s > /dev/null 2>&1');
        if (!$badCmd) { 
            chomp($hostName=`hostname -s`); 
        } else { 
            $badCmd = system('uname -n > /dev/null 2>&1');
            if (!$badCmd) { chomp($hostName=`uname -n`); }
        }
        $badCmd = system('hostname -d > /dev/null 2>&1');
        if (!$badCmd) { chomp($dnsName=`hostname -d`); }
        $badCmd = system('hostname -i > /dev/null 2>&1');
        if (!$badCmd) { chomp($ipAddress=`hostname -i`); }
        $badCmd = system('uname -o > /dev/null 2>&1');
        if (!$badCmd) { chomp($operatingSystem=`uname -o`); }
        $badCmd = system('uname -s > /dev/null 2>&1');
        if (!$badCmd) { chomp($kernelName=`uname -s`); }
        $badCmd = system('uname -r > /dev/null 2>&1');
        if (!$badCmd) { chomp($kernelRelease=`uname -r`); }
        $badCmd = system('uname -v > /dev/null 2>&1');
        if (!$badCmd) { chomp($kernelVersion=`uname -v`); }
        $badCmd = system('uname -p > /dev/null 2>&1');
        if (!$badCmd) { chomp($processor=`uname -p`); }
        $badCmd = system('uname -m > /dev/null 2>&1');
        if (!$badCmd) { chomp($machineHardware=`uname -m`); }
        $badCmd = system('uname -i > /dev/null 2>&1');
        if (!$badCmd) { chomp($hardwarePlatform=`uname -i`); }
            
            # Grab the repository branch tag.
            my $branchTag = "";
            my $homeDirContents = `ls $trilinosDir`;
            if ($homeDirContents =~ m/CVS/) {
                my $cvsDirContents = `ls $trilinosDir/CVS`;
                if ($cvsDirContents =~ m/Tag/) {
                    $branchTag = `cat $trilinosDir/CVS/Tag`;
                    $branchTag =~ s/^T//;
                } else {
                    $branchTag = "development";
                }      
            } else {
                $branchTag = "unknown";
            }
            
            # grab contents of dependencies for reporting purposes.
            my $dependenciesString = "";              
            open (DEPENDENCIES, "<$trilinosDir/commonTools/test/utilities/dependencies");
            undef $/;                               # undefine input record separator
            $dependenciesString=<DEPENDENCIES>;     # copy entire file
            $/ = "\n";                              # restore it to default newline
            close DEPENDENCIES;
            
            # Create results file.
            my $resultsFile = "$resultsDir/$runStartTimeForFilename-configure.txt";
            open (RESULTS, ">$resultsFile")
                or die "can't open configure result file $resultsFile for writing, died";
                
            my $resultsString = "";
            
            $resultsString .= "HOST_NAME            = $hostName\n";
            $resultsString .= "DNS_NAME             = $dnsName\n";
            $resultsString .= "IP_ADDRESS           = $ipAddress\n";
            $resultsString .= "OPERATING_SYSTEM     = $operatingSystem\n";
            $resultsString .= "KERNEL_NAME          = $kernelName\n";
            $resultsString .= "KERNEL_RELEASE       = $kernelRelease\n";
            $resultsString .= "KERNEL_VERSION       = $kernelVersion\n";
            $resultsString .= "PROCESSOR            = $processor\n";
            $resultsString .= "MACHINE_HARDWARE     = $machineHardware\n";
            $resultsString .= "HARDWARE_PLATFORM    = $hardwarePlatform\n";
            $resultsString .= "\n";
            $resultsString .= "TRILINOS_DIR         = $trilinosDir\n";
            $resultsString .= "BRANCH_TAG           = $branchTag\n";
            $resultsString .= "\n";
            $resultsString .= "BUILD_DIR            = $buildDir\n";
            $resultsString .= "\n";
            $resultsString .= "START_TIME           = $runStartTime\n";
            $resultsString .= "STOP_TIME            = $configureStopTime\n";
            $resultsString .= "RUN_TIME             = ".($runSeconds==0?"<1":$runSeconds)." seconds\n";
            $resultsString .= "\n";
            $resultsString .= "EXIT_STATUS          = $configureExitStatus\n";
            $resultsString .= "RESULT               = ".($configureExitStatus==0?"pass":"fail")."\n";
            $resultsString .= "\n";
            $resultsString .= "BROKEN_PACKAGE       = $brokenPackage\n";
            $resultsString .= "\n";
            $resultsString .= "CONFIGURED_PACKAGES {{{\n\n$configuredPackages\n\n}}}\n";
            $resultsString .= "\n";
            $resultsString .= "CONFIGURE_OUTPUT {{{\n\n$configureOutput\n\n}}}\n";
            $resultsString .= "\n";
            $resultsString .= "CONFIG_LOG {{{\n\n$configLog\n\n}}}\n";
            $resultsString .= "\n";
            $resultsString .= "INVOKE_CONFIGURE {{{\n\n$invokeConfigureString\n\n}}}\n";
            $resultsString .= "\n";
            $resultsString .= "DEPENDENCIES {{{\n\n$dependenciesString\n\n}}}\n";
            
            print RESULTS $resultsString;
            
            close RESULTS;
            
            # Append to build info file for database.
            my $buildInfoFile = "$resultsDir/build.txt.tmp1";
            if (stat $buildInfoFile) {
                open (BUILD_FILE, ">>$buildInfoFile")
                    or die "can't open build info file $buildInfoFile for writing, died";            
                my $string = "";          
                $string .= "BRANCH_TAG           = $branchTag\n";  
                $string .= "DEPENDENCIES {{{\n\n$dependenciesString\n\n}}}\n\n";   
                print BUILD_FILE $string;        
                close BUILD_FILE;
                system("mv $buildInfoFile $resultsDir/build.txt.tmp2 > /dev/null 2>&1");
            }
                
            if (!$recover && $configureExitStatus) { return 1; }
                
            if ($eightySix) { return 86; }
            
        } # while(!$configurePassed)
        
        return 0;
        
    } # run()
    
    ############################################################################
    # detectBrokenPackage()
    #
    # Figure out which package failed to configure.
    #
    #   - args:     $configureOutput    (standard out and standard error from configure)
    #
    #   - returns:  $brokenPackage      (name of the package that failed to configure)
    #

    sub detectBrokenPackage {
        my $configureOutput = $_[0];
        
        my $brokenPackage;
        
        # Find last instance of "Running PACKAGE Configure Script."  If it 
        # doesn't have a matching "Finished" banner, assume it's the broken 
        # package.  If it does have a matching "Finished" banner, then check to
        # see if there's a "configuring in" after the "Finished" banner.  If
        # so, then the package in the "configuring in" line is the broken one.
        # If not, we'll blame it on the one in the "Finished" banner.
        $configureOutput =~ m/.*^Running (.*?) Configure Script/ms;
        if (defined $1) { $brokenPackage = $1; }

        if ($configureOutput =~ 
            m/(Finished Running $brokenPackage Configure Script)/) {
            if (defined $1) {
                $configureOutput =~ m/$1.*^configure: configuring in packages\/(\w+)/ms;
                if (defined $1) { $brokenPackage = $1; }
            }
        }
        
        if (defined $brokenPackage) {
            $brokenPackage =~ s/^\s*//;             # trim leading spaces
            $brokenPackage =~ s/\s*$//;             # trim trailing spaces
            $brokenPackage = lc($brokenPackage);    # convert to lower-case
        } else {
            my $message = "error fixing invoke-configure--can't detect package\n";
            printMessage($message, $v1+$v2+$v3);
            return "UNKNOWN";
        }
        
        return $brokenPackage;
        
    } # detectBrokenPackage
    
    ############################################################################
    # detectConfiguredPackages()
    #
    # Figure out which packages configured successfully.
    #
    #   - args:     $configureOutput    (standard out and standard error from configure)
    #
    #   - returns:  $configuredPackages (list of packages that configured successfully)
    #

    sub detectConfiguredPackages {
        my $configureOutput = $_[0];
        
        my $configuredPackages = "";
        
        # Compile list of all "Finished Running PACKAGE Configure Script" strings.        
        my @finishedStrings = $configureOutput =~ m/(Finished( Running)? .*? Configure Script)/g;        
        foreach my $finishedString (@finishedStrings) {
                            
            # Grab package name.
            my $package = "";
            if ($finishedString && $finishedString =~ m/Finished( Running)? (.*?) Configure Script/) {
                $package = $2;
                $package = lc($package);
                $configuredPackages .= "$package; ";
            }
            
        }
        
        return $configuredPackages;
        
    } # detectConfiguredPackages
    
    ############################################################################
    # cleanInvokeConfigure()
    #
    # Remove the given package and its dependents from the invoke-configure.
    #
    #   - args:     $brokenPackage    (name of package to clean from 
    #                                 invoke-configure)
    #
    #   - returns:  NONE
    #

    sub cleanInvokeConfigure {
        my $brokenPackage = $_[0];
        my $brokenSubdir = $_[1];
        my @flagsToInsert = ();
        my $newInvokeConfigure = "";
        my $changeMade = 0;
        
        # Add tests and examples flags to be inserted
        if ($brokenSubdir eq "tests") {
            push (@flagsToInsert, "--disable-$brokenPackage-tests");
        }
        if ($brokenSubdir eq "examples") {
            push (@flagsToInsert, "--disable-$brokenPackage-examples");
        }
    
        # If there isn't a list of actions for this subdirectory, then
        # use the default package-level recovery actions
        if ($brokenSubdir ne "tests" && $brokenSubdir ne "examples" && 
            !exists $dependencies{$brokenPackage}{$brokenSubdir}) {
            $brokenSubdir = "default";
        }
            
        # Open invoke-configure for reading.
        open (INVOKE_CONFIGURE, "<$buildDir/invoke-configure")
            or die "can't open invoke-configure, died";
            
        # Parse it, extracting lines to keep.
        while (my $line = <INVOKE_CONFIGURE>) {
            $line =~ s/^\s*//;  # trim leading spaces
            $line =~ s/\s*$//;  # trim trailing spaces
            # Compare lines to package dependencies.
            my $dropLine = 0;            
            my $lastElementIndex = $#{$dependencies{$brokenPackage}{$brokenSubdir}};
            for (my $i=0; $i<=$lastElementIndex; $i++) { 
                if ($dependencies{$brokenPackage}{$brokenSubdir}[$i] =~ m/REMOVE ((\w|-)+)/) {
                    my $flag = $1;            
                    if ($line =~ m/$flag\b/i) {
                        $dropLine = 1;
                    }                    
                }
            }    
            # Write line if it isn't a dependency.
            if (!$dropLine) {                             
                $newInvokeConfigure .= "$line\n";
            } else {
                $changeMade = 1;
            }
        } # while ($line)   
        
        close INVOKE_CONFIGURE;     

        # Look for INSERT commands
        my $lastElementIndex = $#{$dependencies{$brokenPackage}{$brokenSubdir}};
        for (my $i=0; $i<=$lastElementIndex; $i++) {             
            if ($dependencies{$brokenPackage}{$brokenSubdir}[$i] =~ m/INSERT (.+)/) {
                my $flag = $1;
                push (@flagsToInsert, $flag);
            }            
        } 

        # Open invoke-configure so we can check to see that we don't add a flag
        # that is already present.  Otherwise, the invoke-configure would 
        # perpetually continue to change, but not change functionally at all.
        open (INVOKE_CONFIGURE, "<$buildDir/invoke-configure") or die "can't open invoke-configure, died";
        undef $/;                       # undefine input record separator
        my $file=<INVOKE_CONFIGURE>;    # copy entire file
        $/ = "\n";                      # restore it to default newline
        close INVOKE_CONFIGURE;
        
        # Append all INSERTed flags
        foreach my $flagToInsert (@flagsToInsert) {        
            if ($file !~ m/$flagToInsert/) {
                $newInvokeConfigure .= "$flagToInsert \\\n";
                $changeMade = 1;
            }            
        }
        
        if (!$changeMade) {
            if ($brokenSubdir eq "default") {
                my $message = "error fixing invoke-configure--no changes made ($brokenPackage broke)\n";
                printMessage($message, $v1+$v2+$v3);
                return 86;
            } else {
                return cleanInvokeConfigure($brokenPackage, "default");
            }
        }
        
        # Prepare new invoke-configure.
        $newInvokeConfigure =~ s/\n\s*\n/\n/g;      # remove any blank lines
        $newInvokeConfigure =~ s/^\s*//mg;          # remove leading spaces
        $newInvokeConfigure =~ s/\s*$//mg;          # remove trailing spaces  
        $newInvokeConfigure =~ s/\\$//mg;           # remove line-continuation chars 
        $newInvokeConfigure =~ s/\s*$//mg;          # remove trailing spaces  
        $newInvokeConfigure =~ s/$/ \\/mg;          # insert line-continuation chars  
        $newInvokeConfigure =~ s/(.*)\\$/$1/s;      # remove last line-continuation
        chomp ($newInvokeConfigure);                # remove last newline 
        
        # Open invoke-configure for writing.
        open (INVOKE_CONFIGURE, ">$buildDir/invoke-configure")
            or die "can't open invoke-configure, died";
        
        # Write new invoke configure.
        print INVOKE_CONFIGURE $newInvokeConfigure;
        close INVOKE_CONFIGURE; 
           
        return 0;
           
    } # cleanInvokeConfigure()
    
    ############################################################################
    # parseDependencies()
    #
    # Parses testharness/dependencies and fills global hash of hash of arrays
    # of the form:
    #
    #   ({PACKAGE_A,
    #       {SUBDIR_X,
    #           [optionAX1, optionAX2, ...]}},
    #       {SUBDIR_Y,
    #           [optionAY1, optionAY2, ...]}},
    #   {PACKAGE_B, 
    #       {SUBDIR_X,
    #           [optionB1, ...]}})
    #
    #   - args:     NONE
    #
    #   - returns:  NONE
    #

    sub parseDependencies {
        my $filename = "$trilinosDir/commonTools/test/utilities/dependencies";        
        open (IN_FILE, "<$filename") or die "can't open $filename";
        
        # Read definition file into one string.
        undef $/;               # undefine input record separator
        my $file=<IN_FILE>;     # copy entire file
        $/ = "\n";              # restore it to default newline
        close IN_FILE;
        
        # Clean up file.
        $file =~ s/#.*$//mg;        # remove everything after any #
        $file =~ s/\n\s*\n/\n/g;    # remove any blank lines
        $file =~ s/^\s*//mg;        # remove leading spaces
        $file =~ s/\s*$//mg;        # remove trailing spaces
        
        # break dependencies file into groups
        my @groups = $file =~ m/(IF PACKAGE .*? THEN.*?DONE)/gs;
        
        # process each group
        foreach my $group (@groups) {
        
            if ($group =~ m/IF PACKAGE (\w+)( SUBDIRECTORY (\w+))? THEN(.*?)DONE/s) {
        
                my $package = $1;
                my $subdir = $3;        if (!$subdir) { $subdir = "default"; }
                my $actionsString = $4;               
        
                # initialize hash for this package
                if (!exists $dependencies{$package}) {
                    $dependencies{$package} = ();
                }
                
                # initialize array for this subdir
                if (!exists $dependencies{$package}{$subdir}) {
                    $dependencies{$package}{$subdir} = ();
                }
                
                # break actions list into individual actions
                my @actions = $actionsString =~ m/((?:INSERT|REMOVE) .*?$)/mg;
                
                # push each action onto the list for this package/subdir
                foreach my $action (@actions) {                
                    push (@{$dependencies{$package}{$subdir}}, $action);                
                }
                
            }
        
        }
        
        # print the dependencies hash of arrays for debugging
        printMessage("\n\%dependencies:\n\n", $v3);
        for my $package (keys %dependencies) {
            my %packageHash = %{$dependencies{$package}};
            for my $subdir (keys %packageHash) {
                my $lastElementIndex = $#{$packageHash{$subdir}};
                printMessage("  $package / $subdir (".($lastElementIndex+1)."): \n", $v3);
                for my $i (0 .. $lastElementIndex) {
                    printMessage("    $i = ".$packageHash{$subdir}[$i]."\n", $v3);
                }
            }
        }            
        
    } # parseDependencies()
    
    ############################################################################
    # getDirName()
    #
    # Given the standardized name of a package, return the directory name.
    #
    #   - args:     $packageName    (standardized package name)
    #
    #   - returns:                  (directory name)
    #

    sub getDirName {
        my $packageName = $_[0];       
        
        my $filename = "$trilinosDir/commonTools/test/utilities/packages";        
        open (IN_FILE, "<$filename") or die "can't open $filename";
        
        # Read definition file into one string.
        undef $/;               # undefine input record separator
        my $file=<IN_FILE>;     # copy entire file
        $/ = "\n";              # restore it to default newline
        close IN_FILE;
        
        # Clean up file.
        $file =~ s/#.*$//mg;        # remove everything after any #
        $file =~ s/\n\s*\n/\n/g;    # remove any blank lines
        $file =~ s/^\s*//mg;        # remove leading spaces
        $file =~ s/\s*$//mg;        # remove trailing spaces
        
        # break packages file into groups
        my %dirNames = $file =~ m/STD_NAME:\s*(\w+),\s*DIR_NAME:\s*(\w+)/gs;
        
        return $dirNames{$packageName}
                           
    } # getDirName()
    
    ############################################################################
    # cleanUp()
    #
    # Clean up environment variables, temp files, etc.
    #
    #   - args:     NONE
    #
    #   - returns:  NONE
    #

    sub cleanUp {
    
        # Currently, there is nothing to clean up, but I will leave this
        # subroutine here for potential future use.
           
    } # cleanUp()
    
    ############################################################################
    # printMessage()
    #
    # Prints an event if the verbosity is set.
    #
    #   - args:     $message        (message to be printed)
    #               $level          (verbosity level of message)
    #
    #   - returns:  NONE
    #

    sub printMessage {
        my $message = $_[0];
        my $level = $_[1];
        
        if ($verbosity & $level) {
            print $message;
        }
        
        if ($logVerbosity & $level) {
            my $log = $resultsDir."/log.txt";
            open (LOG, ">>$log")
                or die "can't open $log";
            print LOG $message;
            close LOG;
        }
    } # printMessage()

    ############################################################################
    # printHelp()
    #
    # Prints help output.
    #
    #   - args:     NONE
    #
    #   - returns:  NONE
    #

    sub printHelp {
        print "runconfigure - The Trilinos Configure Utility\n";
        print "\n";
        print "Usage:  perl runconfigure --trilinos-dir=/home/user/Trilinos --build-dir=MPI\n";
        print "\n";
        print "Options:\n";
        print "\n";
        print "  --trilinos-dir=DIR         Specify the absolute path to the top-level\n";
        print "                             Trilinos directory that contains this program.\n";
        print "                             Example: /home/user/Trilinos\n";
        print "                             REQUIRED.\n";
        print "\n";
        print "  --build-dir=DIR            Specify the name of the build directory where you\n";
        print "                             would like to configure Trilinos.  If a relative\n";
        print "                             path is given, it is assumed to be in the given\n";
        print "                             Trilinos directory.  IMPORTANT: regardless of\n";
        print "                             whether this is a relative or absolute path,\n";
        print "                             runconfigure will do an 'rm -rf' on this direcotry\n";
        print "                             to blow it away before creating it and proceeding.\n";
        print "                             REQUIRED.\n";
        print "\n";
        print "  --invoke-configure=FILE    Relative paths to a complete invoke-configure file.\n";
        print "                             REQUIRED.\n";
        print "\n";
        print "  --recover                  If this flag is present, runconfigure will attempt\n";
        print "                             to remove a broken package and its dependents and\n";
        print "                             continue until some subset configures\n";
        print "                             successfully.\n";
        print "\n";
        print "  --output-dir=DIR           Specify the directory in which to create the\n";
        print "                             directory containing the results.\n";
        print "                             Default: \".\"\n";
        print "\n";
        print "  --verbosity=LEVEL          0 = no non-fatal ouput (same as --quiet)\n";
        print "                             1 = normal output (default)\n";
        print "                             2 = level 2 verbosity\n";
        print "                             3 = level 3 verbosity\n";
        print "\n";
        print "  --log-verbosity=LEVEL      0 = no log\n";
        print "                             1 = normal output (default)\n";
        print "                             2 = level 2 verbosity\n";
        print "                             3 = level 3 verbosity\n";
        print "\n";
        print "  --quiet                    Produce no non-fatal output.\n";
        print "\n";
        print "  --help                     Print this help output and exit.\n";
        print "\n";
        print "Notes:\n";
        print "  - For more information, see README-runconfigure in\n";
        print "    Trilinos/commonTools/test/utilities/\n";
        print "    or visit http://software.sandia.gov/trilinos/developer/\n";
        print "\n";
    } # printHelp()
