Blame maint/gen_subcfg_m4

Packit 0848f5
#!/usr/bin/env perl
Packit 0848f5
# (C) 2011 by Argonne National Laboratory.
Packit 0848f5
#     See COPYRIGHT in top-level directory.
Packit 0848f5
Packit 0848f5
# This script scavenges the MPICH source tree looking for "subconfigure.m4"
Packit 0848f5
# files.  It then analyzes the dependencies between them and emits a
Packit 0848f5
# "subsys_include.m4" file that "m4_include"s them all in the correct order and
Packit 0848f5
# writes an autoconf macro that is suitable for expanding subsys macros in the
Packit 0848f5
# correct order.
Packit 0848f5
#
Packit 0848f5
# Finding a "dnl MPICH_SUBCFG_BEFORE=BAR" statement inside of
Packit 0848f5
# FOO/subconfigure.m4 means that FOO depends on BAR and that FOO's macros should
Packit 0848f5
# be emitted *before* BAR's macros so that FOO can influence BAR's
Packit 0848f5
# configuration.
Packit 0848f5
#
Packit 0848f5
# Finding a "dnl MPICH_SUBCFG_AFTER=QUUX" statement inside of
Packit 0848f5
# BAZ/subconfigure.m4 means that BAZ uses QUUX and that BAZ's macros be emitted
Packit 0848f5
# *after* QUUX's macros so that BAZ can utilize information exported by QUUX's
Packit 0848f5
# configuration macros.
Packit 0848f5
#
Packit 0848f5
# We have both forms of macros because some subsystems know their consumers
Packit 0848f5
# explicitly and some packages only know what they consume explicitly, and some
Packit 0848f5
# packages are a blend.  For example, ch3 depends on the mpid/common/sched code
Packit 0848f5
# in order to support NBC ops so a BEFORE statement is used in the
Packit 0848f5
# ch3/subconfigure.m4 in order to "enable_mpid_common_sched=yes".  The nemesis
Packit 0848f5
# netmods all depend on the core nemesis configuration happening first, so they
Packit 0848f5
# use an AFTER statement in each of their subconfigure.m4 files.
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
use strict;
Packit 0848f5
use warnings;
Packit 0848f5
Packit 0848f5
use Getopt::Long;
Packit 0848f5
use Data::Dumper;
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# Default global settings and constants
Packit 0848f5
Packit 0848f5
# unlikely to change, since this name is assumed in lots of other pieces of the
Packit 0848f5
# build system
Packit 0848f5
my $SUBCFG_NAME = "subconfigure.m4";
Packit 0848f5
my $OUTPUT_FILE = "subsys_include.m4";
Packit 0848f5
Packit 0848f5
# if true, add a dependency (ancestor-->child) when $SUBCFG_NAME files are
Packit 0848f5
# encountered and they have an ancestor $SUBCFG_NAME file in an enclosing
Packit 0848f5
# directory
Packit 0848f5
my $USE_IMPLICIT_EDGES = 1;
Packit 0848f5
Packit 0848f5
# the existence of this file means we should stop recursing down
Packit 0848f5
# the enclosing directory tree
Packit 0848f5
my $stop_sentinel = ".no_subcfg_recursion";
Packit 0848f5
Packit 0848f5
# which directories should be recursively searched for "subconfigure.m4" files
Packit 0848f5
my @root_dirs = qw( src );
Packit 0848f5
Packit 0848f5
# coloring constants for the graph algorithms later on
Packit 0848f5
my ($WHITE, $GRAY, $BLACK) = (1, 2, 3);
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# Option processing: here's a great place to permit overriding the default
Packit 0848f5
# global settings from above if we ever need to in the future.
Packit 0848f5
Packit 0848f5
my $do_bist = 0;
Packit 0848f5
GetOptions(
Packit 0848f5
    "--help" => \&print_usage,
Packit 0848f5
    "--bist" => \$do_bist,
Packit 0848f5
) or die "unable to process options, stopped";
Packit 0848f5
Packit 0848f5
sub print_usage {
Packit 0848f5
    print <
Packit 0848f5
This script builds '$OUTPUT_FILE' from '$SUBCFG_NAME' files.
Packit 0848f5
Packit 0848f5
Usage: $0 [--help] [--bist]
Packit 0848f5
Packit 0848f5
    --bist - Run simple self tests on this script.
Packit 0848f5
    --help - This message.
Packit 0848f5
EOT
Packit 0848f5
    exit 0;
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
if ($do_bist) {
Packit 0848f5
    bist();
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# preorder traverse the root dirs looking for files named $SUBCFG_NAME
Packit 0848f5
Packit 0848f5
# stack for recursion, contains dirs that must yet be visited
Packit 0848f5
my @dirstack = ( @root_dirs );
Packit 0848f5
# Parallel stack that keeps track of the nearest ancestor with a subconfigure.m4
Packit 0848f5
# file.  All root dirs have no ancestors.
Packit 0848f5
my @anc_stack = map { '' } @root_dirs;
Packit 0848f5
Packit 0848f5
# keys are the full path to the found file, value is the nearest ancestor (in
Packit 0848f5
# the directory hierarchy sense) subconfigure.m4 file, or '' if none exists.
Packit 0848f5
my %found_files = ();
Packit 0848f5
Packit 0848f5
while (my $dir = pop @dirstack) {
Packit 0848f5
    my $anc = pop @anc_stack;
Packit 0848f5
Packit 0848f5
    # check for a $SUBCFG_NAME before recursing in order to correctly propagate
Packit 0848f5
    # ancestor information for subdirectories
Packit 0848f5
    my $fp = "$dir/$SUBCFG_NAME";
Packit 0848f5
    if (-e $fp) {
Packit 0848f5
        # found a subconfigure.m4 file
Packit 0848f5
        $found_files{$fp} = $anc;
Packit 0848f5
        # override our parent's ancestor for all of our descendants
Packit 0848f5
        $anc = $fp;
Packit 0848f5
    }
Packit 0848f5
Packit 0848f5
    if (-e "$dir/$stop_sentinel") {
Packit 0848f5
        # the existence of this file means we should stop recursing down
Packit 0848f5
        # this particular directory tree
Packit 0848f5
        next;
Packit 0848f5
    }
Packit 0848f5
Packit 0848f5
    # now that we've visited the current vertex, push all of our child dirs onto
Packit 0848f5
    # the stack to continue the traversal
Packit 0848f5
    opendir DH, $dir
Packit 0848f5
        or die "unable to open dir='$dir', stopped";
Packit 0848f5
    my @contents = readdir DH;
Packit 0848f5
    foreach my $f (@contents) {
Packit 0848f5
        # avoid endless recursion
Packit 0848f5
        next if $f eq "." || $f eq "..";
Packit 0848f5
Packit 0848f5
        if (-d "$dir/$f") {
Packit 0848f5
            push @dirstack, "$dir/$f";
Packit 0848f5
            push @anc_stack, $anc;
Packit 0848f5
        }
Packit 0848f5
    }
Packit 0848f5
    closedir DH;
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# We now have a list of all $SUBCFG_NAME files in @found_files.  Process each of
Packit 0848f5
# the files and build a DAG.
Packit 0848f5
Packit 0848f5
# A DAG where the vertices are full path filenames for $SUBCFG_NAME files and the
Packit 0848f5
# edges are dependencies between the $SUBCFG_NAME files (A-->B indicates that
Packit 0848f5
# A should come before B in a topo sort).  We concretely express this DAG as an
Packit 0848f5
# adjacency list stored in a hash.  Keys of the hash are filenames, values are
Packit 0848f5
# refs to hashes whose keys are outbound edge filenames.
Packit 0848f5
# IOW:
Packit 0848f5
#     ( a => {b=>1,c=>1}, b => {c=>1}, c => {} )
Packit 0848f5
# represents the following (crudely drawn) graph:
Packit 0848f5
#     a-->b-->c
Packit 0848f5
#      \------^
Packit 0848f5
my %dag = ();
Packit 0848f5
Packit 0848f5
# Helper routine, adds a new edge to the given dag hash (passed by reference),
Packit 0848f5
# automatically creating src or dst vertices as necessary.  A cute bit of calling
Packit 0848f5
# syntax is that the src and dst args can be separated by "=>" because it's just
Packit 0848f5
# a fancy comma in perl.
Packit 0848f5
sub add_edge {
Packit 0848f5
    my $dag_ref = shift;
Packit 0848f5
    my $src = shift;
Packit 0848f5
    my $dst = shift;
Packit 0848f5
Packit 0848f5
    die "\$dag_ref is invalid, stopped" unless ref($dag_ref) eq "HASH";
Packit 0848f5
    die "\$src is invalid, stopped" unless $src;
Packit 0848f5
    die "\$dst is invalid, stopped" unless $dst;
Packit 0848f5
Packit 0848f5
    $dag_ref->{$src} = {} unless exists $dag_ref->{$src};
Packit 0848f5
    $dag_ref->{$src}->{$dst} = 1;
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
foreach my $k (keys %found_files) {
Packit 0848f5
    # add the vertex to the graph with no edges to start
Packit 0848f5
    $dag{$k} = {} unless exists $dag{$k};
Packit 0848f5
Packit 0848f5
    my $anc = $found_files{$k};
Packit 0848f5
    if ($anc and $USE_IMPLICIT_EDGES) {
Packit 0848f5
        # need to add the implicit edge from the ancestor to $k
Packit 0848f5
        add_edge(\%dag, $anc => $k);
Packit 0848f5
    }
Packit 0848f5
Packit 0848f5
    # now process the file and add any explicit edges
Packit 0848f5
    open FILE, '<', $k;
Packit 0848f5
    while (my $line = <FILE>) {
Packit 0848f5
        if ($line =~ m/^\s*dnl +MPICH_SUBCFG_([A-Z_]*)=(.*)\s*$/) {
Packit 0848f5
            my $bef_aft = $1;
Packit 0848f5
            my $arg = $2;
Packit 0848f5
Packit 0848f5
            # users can set GEN_SUBCFG_NO_ERROR=1 in the environment to prevent
Packit 0848f5
            # this script from complaining about missing '/' chars
Packit 0848f5
            if ($arg !~ m|/| and not $ENV{GEN_SUBCFG_NO_ERROR}) {
Packit 0848f5
                print "ERROR: no '/' characters detected in '$arg', possible old-style structured comment still present\n";
Packit 0848f5
                exit 1;
Packit 0848f5
            }
Packit 0848f5
Packit 0848f5
            # normalize the $arg to match our DAG
Packit 0848f5
            $arg .= "/$SUBCFG_NAME";
Packit 0848f5
Packit 0848f5
            if ($bef_aft eq "BEFORE") {
Packit 0848f5
                add_edge(\%dag, $k => $arg);
Packit 0848f5
            }
Packit 0848f5
            elsif ($bef_aft eq "AFTER") {
Packit 0848f5
                add_edge(\%dag, $arg => $k);
Packit 0848f5
            }
Packit 0848f5
            else {
Packit 0848f5
                die "unrecognized structured comment ('MPICH_SUBCFG_${bef_aft}')\n".
Packit 0848f5
                    "at $k:$., possible typo?  Stopped";
Packit 0848f5
            }
Packit 0848f5
        }
Packit 0848f5
    }
Packit 0848f5
    close FILE;
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# We now have a DAG expressing the dependency information between the various
Packit 0848f5
# subconfigure.m4 files.  Now we need to topologically sort it.
Packit 0848f5
# 
Packit 0848f5
# We use the topo sort algorithm given in "Introduction to Algorithms" (1st
Packit 0848f5
# ed.), page 486 with a small modification to detect cycles in the digraph.  We
Packit 0848f5
# perform a DFS on the DAG, coloring vertices as we go.  We could compute
Packit 0848f5
# discovery and finishing times, as well as predecessors, but we don't need that
Packit 0848f5
# information for topological sorting or cycle detection.  As each vertex is
Packit 0848f5
# finished (colored BLACK) we prepend it to an array.  The resulting array is
Packit 0848f5
# sorted in "ascending" topological order (a,b,c in our previous example).
Packit 0848f5
Packit 0848f5
# the output array in which the sorted results will be stored
Packit 0848f5
my @tsorted = ();
Packit 0848f5
topo_sort(\%dag, \@tsorted);
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# Now just emit the $OUTPUT_FILE in the correct format.
Packit 0848f5
Packit 0848f5
open OUTFD, '>', $OUTPUT_FILE;
Packit 0848f5
Packit 0848f5
my $datestamp = scalar(localtime);
Packit 0848f5
Packit 0848f5
print OUTFD <
Packit 0848f5
dnl generated by $0 at $datestamp
Packit 0848f5
dnl DO NOT EDIT BY HAND!!!
Packit 0848f5
dnl re-run ./maint/updatefiles instead
Packit 0848f5
Packit 0848f5
EOT
Packit 0848f5
foreach my $file (@tsorted) {
Packit 0848f5
    print OUTFD "m4_include([$file])\n";
Packit 0848f5
}
Packit 0848f5
print OUTFD <
Packit 0848f5
Packit 0848f5
dnl a macro suitable for use as m4_map([some_unary_macro],[PAC_SUBCFG_MODULE_LIST])
Packit 0848f5
m4_define([PAC_SUBCFG_MODULE_LIST],
Packit 0848f5
m4_dquote(
Packit 0848f5
EOT
Packit 0848f5
Packit 0848f5
foreach my $file (@tsorted[0 .. $#tsorted-1]) {
Packit 0848f5
    my $mod_name = $file;
Packit 0848f5
    $mod_name =~ s+/$SUBCFG_NAME$++;
Packit 0848f5
    $mod_name =~ tr+/+_+;
Packit 0848f5
    print OUTFD "[$mod_name],\n";
Packit 0848f5
}
Packit 0848f5
my $mod_name = $tsorted[-1];
Packit 0848f5
$mod_name =~ s+/$SUBCFG_NAME$++g;
Packit 0848f5
$mod_name =~ tr+/+_+;
Packit 0848f5
print OUTFD "[$mod_name]dnl <--- this dnl is important\n";
Packit 0848f5
print OUTFD "))\n\n";
Packit 0848f5
Packit 0848f5
close OUTFD;
Packit 0848f5
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# SUBROUTINES
Packit 0848f5
################################################################################
Packit 0848f5
Packit 0848f5
# The DFS-Visit(u) algorithm specialized for topo sorting.  Currently a
Packit 0848f5
# subroutine to permit recursive invocation, but could be converted to use an
Packit 0848f5
# explicit stack and the subroutine could be eliminated.
Packit 0848f5
#
Packit 0848f5
# takes four arguments: a ref to the DAG hash, a ref to a colors hash, the
Packit 0848f5
# vertex $u, and an output array reference to be populated as vertices are
Packit 0848f5
# finished.
Packit 0848f5
sub dfs_visit {
Packit 0848f5
    my $dag_ref = shift;
Packit 0848f5
    my $colors_ref = shift;
Packit 0848f5
    my $u = shift;
Packit 0848f5
    my $out_arr_ref = shift;
Packit 0848f5
Packit 0848f5
    $colors_ref->{$u} = $GRAY;
Packit 0848f5
    foreach my $v (keys %{$dag_ref->{$u}}) {
Packit 0848f5
        # detect cycles in the graph at this point, see ("Classification of edges"
Packit 0848f5
        # in CLR)
Packit 0848f5
        if ($colors_ref->{$v} == $GRAY) {
Packit 0848f5
            # We are already exploring the tree from $v, so this is a "back edge",
Packit 0848f5
            # indicating a cycle is present in the digraph.  This is erroneous in
Packit 0848f5
            # our usage, since we cannot topologically sort a cyclic graph.
Packit 0848f5
            die "A back edge was found in the digraph but a DAG is required.\n".
Packit 0848f5
                "The back edge was from\n".
Packit 0848f5
                "  $u\n".
Packit 0848f5
                "to\n".
Packit 0848f5
                "  $v\n".
Packit 0848f5
                "Stopped";
Packit 0848f5
        }
Packit 0848f5
        elsif ($colors_ref->{$v} == $WHITE) {
Packit 0848f5
            dfs_visit($dag_ref, $colors_ref, $v, $out_arr_ref);
Packit 0848f5
        }
Packit 0848f5
    }
Packit 0848f5
    $colors_ref->{$u} = $BLACK;
Packit 0848f5
Packit 0848f5
    # append $u to the output
Packit 0848f5
    unshift @$out_arr_ref, $u;
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
# Takes two arguments, a ref to a DAG hash and a reference to an output array.
Packit 0848f5
# Returns in the output array a valid topological sort of the given DAG.
Packit 0848f5
sub topo_sort {
Packit 0848f5
    my $dag_ref = shift;
Packit 0848f5
    my $out_arr_ref = shift;
Packit 0848f5
Packit 0848f5
    # helper hash that is indexed by vertex name in order to avoid building a
Packit 0848f5
    # complicated set of nested structures inside the main DAG
Packit 0848f5
    my $colors_ref = {}; # values are one of $WHITE, $GRAY, or $BLACK
Packit 0848f5
Packit 0848f5
    # a simplified version of the DFS(G) algorithm
Packit 0848f5
    foreach my $u (keys %$dag_ref) {
Packit 0848f5
        $colors_ref->{$u} = $WHITE;
Packit 0848f5
    }
Packit 0848f5
    foreach my $u (keys %$dag_ref) {
Packit 0848f5
        if ($colors_ref->{$u} == $WHITE) {
Packit 0848f5
            dfs_visit($dag_ref, $colors_ref, $u, $out_arr_ref);
Packit 0848f5
        }
Packit 0848f5
    }
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
################################################################################
Packit 0848f5
# self tests
Packit 0848f5
Packit 0848f5
# run this subroutine to self-test portions of this script
Packit 0848f5
sub bist {
Packit 0848f5
    bist_topo_sort();
Packit 0848f5
    print "all self-tests PASSED\n";
Packit 0848f5
    exit 0;
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
sub bist_topo_sort {
Packit 0848f5
    my $dag;
Packit 0848f5
    my $out_arr;
Packit 0848f5
    my $expected;
Packit 0848f5
Packit 0848f5
    $dag = { a => {b=>1,c=>1}, b => {c=>1}, c => {} };
Packit 0848f5
    $out_arr = [];
Packit 0848f5
    $expected = [ qw(a b c) ];
Packit 0848f5
    topo_sort($dag, $out_arr);
Packit 0848f5
    cmp_arrays($out_arr, $expected);
Packit 0848f5
Packit 0848f5
    $dag = { a => {}, b => {}, c => {} };
Packit 0848f5
    $out_arr = [];
Packit 0848f5
    topo_sort($dag, $out_arr);
Packit 0848f5
    # this DAG has no single expected result, so just check lengths
Packit 0848f5
    unless (scalar @$out_arr eq scalar @$expected) {
Packit 0848f5
        die "\$out_arr and \$expected differ in length, stopped\n";
Packit 0848f5
    }
Packit 0848f5
Packit 0848f5
    my $routine = (caller(0))[3];
Packit 0848f5
    print "$routine PASSED\n";
Packit 0848f5
}
Packit 0848f5
Packit 0848f5
sub cmp_arrays {
Packit 0848f5
    my $out_arr = shift;
Packit 0848f5
    my $expected = shift;
Packit 0848f5
Packit 0848f5
    #print "out_arr=".Dumper($out_arr)."\n";
Packit 0848f5
    #print "expected=".Dumper($expected)."\n";
Packit 0848f5
    unless (scalar @$out_arr eq scalar @$expected) {
Packit 0848f5
        die "\$out_arr and \$expected differ in length, stopped\n";
Packit 0848f5
    }
Packit 0848f5
    for (my $i = 0; $i < @$out_arr; ++$i) {
Packit 0848f5
        unless ($out_arr->[$i] eq $expected->[$i]) {
Packit 0848f5
            die "element $i of \$out_arr differs from the expected value (".
Packit 0848f5
                $out_arr->[$i]." ne ".$expected->[$i]."), stopped\n";
Packit 0848f5
        }
Packit 0848f5
    }
Packit 0848f5
}
Packit 0848f5