#!/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]
# (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
GetOptions("include=s{0,}", \@inclist);
@inclist = grep { $_ ne '' } @inclist;  # Remove empty element when no argument is given...

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

our %moddecl = ();
our %modused = ();
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 (@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 ($myinc ~~ @inclist) {
        # This include file is found in source directory: dependency "as is"
        push(@mydeps,$myinc);
      } elsif ($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 (%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) {
# print $fh "\n";
# foreach $myfile (sort keys %modused) {  # For all files
#   $mylist = "";
#   foreach $mymod (keys %{$modused{$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 (%modused) {
  print $fh "\n";
  print $fh "# Fortran file dependencies on the module files:\n";
  foreach $myfile (sort keys %modused) {  # For all files
    @mydeps = ();
    foreach $mymod (keys %{$modused{$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 ($moddecl{$mymod} ~~ @mydeps)) {
            push(@mydeps,$moddecl{$mymod});
          }
        }
      }
    }
    if (@mydeps) {
      print $fh $dir.$myfile.":";
      foreach $dep (sort @mydeps) {
        print $fh " ".$dir.$dep;
      }
      print $fh "\n";
    }
  }
}


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

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


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

  my($filename) = @_;

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

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

  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 ($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{$filename}{$modname} = 1;
    }

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

  }

  close(IN);

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