#!/usr/bin/perl -w

# Process module dependencies (where they come from and where they
# are used) at the level of a single library. Write corresponding
# Makefile dependencies so that parallel make works correctly.
#
# Usage:
#   gag-makedepend.pl List-of-Fortran-files
#                     [--include List-of-include-files]
#                     [--digraph]
#                     [--help]
# (e.g. gag-makedepend.pl *.f90)
#
# will read all the named files, build the dependencies, and print them
# to STDOUT.

use Getopt::Long;

# Retrieve options
my $digraph = '';
my $help = '';
GetOptions('digraph' => \$digraph,
           'help' => \$help,
           "include=s{0,}", \@inclist)
  or do {
    print STDERR "Try '$0 --help' for usage.\n";
    exit 1;
  };
@inclist = grep { $_ ne '' } @inclist;  # Remove empty element when no argument is given...

if ($help) {
  print_help();
  exit 0;
}


### 1 - File parsing ###################################################

our %moddecl = ();
our %modused_infile = ();
our %modused_inmod = ();
our %incused = ();

# Module and include parsing
foreach $myfile (@ARGV) {
  parse_onefile($myfile);
}


### 2 - Open outfile file ##############################################

# For now output to STDOUT, but could be custom with an option.
$fh = *STDOUT;


### 3 - Print dependencies #############################################

$dir = "\$(builddir)/";

### 3.1 Include files ###

if (not $digraph) {
if (@inclist) {
  print $fh "\n";
  print $fh "# Fortran file dependencies on the include files:\n";
  foreach $myfile (sort keys %incused) {  # For all files
    @mydeps = ();
    foreach $myinc (sort keys %{$incused{$myfile}}) {  # For all include used in this file
      if (list_contains($myinc, @inclist)) {
        # This include file is found in source directory: dependency "as is"
        push(@mydeps,$myinc);
      } elsif (list_contains($myinc."pp", @inclist)) {
        # This include file is NOT found in source directory, but an ".incpp"
        # version is found in local directory: dependency on the preprocessed
        # file in $builddir
        push(@mydeps,$dir.$myinc);
      }
    }
    if (@mydeps) {
      print $fh $dir.$myfile.":";
      foreach $dep (@mydeps) {
        print $fh " ".$dep;
      }
      print $fh "\n";
    }
  }
}
}


### 3.2 Modules and the file they come from (this is needed for LIB_EXPORTS) ###

if (not $digraph) {
if (%moddecl) {
  print $fh "\n";
  print $fh "# Module dependencies on their parent file:\n";
  foreach $mymod (sort keys %moddecl) {
    print $fh $dir.$mymod.".mod: ".$dir.$moddecl{$mymod}."\n";
  }
}
}


### 3.3 List files and the modules they use ###

# if (%modused_infile) {
# print $fh "\n";
# foreach $myfile (sort keys %modused_infile) {  # For all files
#   $mylist = "";
#   foreach $mymod (keys %{$modused_infile{$myfile}}) {  # For all module used in this file
#     if (defined($moddecl{$mymod})) {  # If this module is defined in this library
#       if ($moddecl{$mymod} ne $myfile) {  # If this module is not declared in the current file
#         $mylist = $mylist." ".$dir."$mymod.mod"
#       }
#     }
#   }
#   if ($mylist ne "") {
#     print $fh $dir.$myfile.":".$mylist."\n";
#   }
# }
# }


### 3.4 List files and the files they depend on ###

if (not $digraph) {
if (%modused_infile) {
  print $fh "\n";
  print $fh "# Fortran file dependencies on the module files:\n";
  foreach $myfile (sort keys %modused_infile) {  # For all files
    @mydeps = ();
    foreach $mymod (keys %{$modused_infile{$myfile}}) {  # For all modules used in this file
      if (defined($moddecl{$mymod})) {  # If this module is defined in this library
        if ($moddecl{$mymod} ne $myfile) {  # If this module is not declared in the current file
          if (not list_contains($moddecl{$mymod}, @mydeps)) {
            push(@mydeps,$moddecl{$mymod});
          }
        }
      }
    }
    if (@mydeps) {
      print $fh $dir.$myfile.":";
      foreach $dep (sort @mydeps) {
        print $fh " ".$dir.$dep;
      }
      print $fh "\n";
    }
  }
}
}

### 3.5 List modules and the modules they depend on ###

if ($digraph) {
if (%modused_inmod) {
  print $fh "digraph modules {\n";
  print $fh "rankdir=BT;\n";
  print $fh "node [shape = ellipse];\n";
  foreach $myhostmod (sort keys %modused_inmod) {  # For all modules
    @mydeps = ();
    foreach $myguestmod (keys %{$modused_inmod{$myhostmod}}) {  # For all guest modules used in this host module
      if (defined($moddecl{$myguestmod})) {  # If this module is defined in this library
        if (not list_contains($myguestmod, @mydeps)) {
          push(@mydeps,$myguestmod);
        }
      }
    }
    $myhostmod =~ s/_/\\n/g;
    if (@mydeps) {
      foreach $dep (sort @mydeps) {
        if (not ($dep =~ /_messaging/)) {  # Skip *_messaging module (too many and not relevant)
          $dep =~ s/_/\\n/g;
          print $fh '"'.$myhostmod.'" -> "'.$dep.'";'."\n";
        }
      }
    }
  }
  print $fh "}\n";
}
}

### 4 - Cleaning #######################################################

if ($fh ne *STDOUT) {
  close($fh) || die "Cannot close output file: $!\n";
}

#=======================================================================
sub list_contains {

  my ($needle, @haystack) = @_;

  foreach my $item (@haystack) {
    if ($item eq $needle) {
      return 1;
    }
  }

  return 0;

}
#=======================================================================
sub print_help {

  print <<EOF;
Usage: $0 Fortran-file [Fortran-file ...]
           [--include Include-file [Include-file ...]]
           [--digraph]
           [--help]

Read the named Fortran source files, extract module and include
dependencies, and print Makefile-style dependencies to STDOUT.

Options:
  --include  List include files that may appear in INCLUDE statements.
             If only a local .incpp version exists, emit the
             corresponding dependency in \$(builddir).
  --digraph  Print module dependencies in Graphviz dot format instead
             of Makefile dependencies.
  --help     Print this help text and exit.

Examples:
  $0 *.f90
  $0 --include common.inc local.incpp *.f90
  $0 --digraph *.f90
EOF

}
#=======================================================================
sub parse_onefile {

  my($filename) = @_;

  open(IN,$filename) || die "Cannot open input file $filename: $!\n";

  $filename =~ s/\.f90/.o/;
  $modcurrent = "";

  while (defined($oneline = <IN>)) {

    if ($oneline =~ /^\s*module\s+(\w+)\s*/i) {
      $modname = lc $1;  # Beware module file name will be lower cased on disk =>
                         #   must be lowercased so that 'make' is not lost
      $moddecl{$modname} = $filename;
      if ($modcurrent eq "") { $modcurrent = $modname; }
    }

    if ($oneline =~ /^\s*end\s+module\s+$modcurrent\s*/i) {
      $modcurrent = "";
    }

    if ($oneline =~ /^\s*use\s+(\w+)\s*/i) {
      $modname = lc $1;  # Beware module file names have been lower cased =>
                         #   module names will be compared all lowercased
      $modused_infile{$filename}{$modname} = 1;
      if ($modcurrent ne "") {
        $modused_inmod{$modcurrent}{$modname} = 1;
      }
    }

    if ($oneline =~ /^\s*include\s+'([\w\.]+)'/i) {
      $incused{$filename}{$1} = 1;
    }

  }

  close(IN);

}
#=======================================================================
