Blame maint/gen_subcfg_m4

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