#!/usr/bin/perl -w
#
# Simple script for automatic dependency generation of (primarily) FORTRAN
# and C programs. See usage() for more info.
#
# Notes
# 1. Some files contain more than one module. When this occurs, the basename
#    of the .mod files will not necessarily match the basename of the source
#    file in which they are found.
# 2. Because of #1, module dependencies must be made to the .mod file rather
#    than to the source or object file (since there may not be a source or
#    object file to match the module name).
# 3. Because of #2, a line is added showing the dependency of the .mod file
#    to object file associated with the source file.
#
# REVISION HISTORY:
# 25Feb2005  da Silva  First crack.
# 17Mar2006  da Silva  Now it should work with more than 1 module per file
# 28Nov2006  da Silva  Fixed Include regular expression.
# 03Mar2007  da Silva  Fixed major bug with .mod dependencies; also revised
#                      the include refular expression rule and added elsif's
#                      for efficiency
# 17Jul2008  Stassi    Divided code into subs; use strict standards
# 17May2011  Stassi    Simplified .mod.o dependency
#........................................................................
use strict;

# global variables
#-----------------
my ($verbose, $outfile, $default);
my ($script, $srcfn, $infile);
my (%deps, $mstring);
my ($base, $suffix);
my ($ncase, $mcase);

# main program
#-------------
{
    init();
    read_input();
    write_output();
}

#=======================================================================
# name - init
# purpose - read input flags and parameters; determine file names;
#           set global variables
#=======================================================================
sub init {
    use File::Basename;
    use Getopt::Long;
    my ($writefile, $mncase, $ifilenm, $help);
    my ($name, $mod, $path);

    $script = basename($0);
    $writefile = 0;
    $outfile = "";

    # command line options
    #---------------------
    GetOptions("v"    => \$verbose,
               "c"    => \$writefile,
               "p=s"  => \$mncase,
               "o=s"  => \$outfile,
               "i=s"  => \$ifilenm,
               "h"    => \$help,
               "help" => \$help);
    usage() if $help;

    # determine name and mod case
    #----------------------------
    # IMPORTANT: never change the line below as installation relies on it
    #            for creating appropiate defaults for each compiler.
  default: $default = "fdp.mod";  
    #----------------------------
    $ncase = "lower";
    $mcase = "lower";

    $mncase = $default unless $mncase;
    ($name, $mod) = split /\./, $mncase;
    $ncase = "upper" if ($name eq uc $name);
    $mcase = "upper" if ($mod  eq uc $mod);

    # input file and input filename
    #------------------------------
    $infile = shift @ARGV;
    die "$script: must supply input filename as input parameter."
        unless ($infile);
    
    # (NOTE: $ifilenm may differ from $infile)
    #-----------------------------------------
    $ifilenm = $infile unless $ifilenm;
    ($base,$path,$suffix) = fileparse($ifilenm,'\..*');
    $srcfn = "$base$suffix";

    # output dependency file
    #-----------------------
    if ($writefile) { $outfile = "$base.d" unless $outfile };

    # initialize global variables
    #----------------------------
    %deps = ();
    $mstring = "";   # string of space-delimited mod names
}

#=======================================================================
# name - read_input
# purpose - read the input file and extract list of dependencies
#
# key variables -
#  %deps : hash storing dependency names as keys
#  $mstring : string containing list of dependency names
#=======================================================================
sub read_input {
    my ($keyword, $name, @dummy);

    open INFILE, "< $infile"
        or die "$script: >>> Error <<< while opening input file: $infile: $!";
    
    while (<INFILE>) {
        chomp;
        s/^\s*//;        # remove leading blanks
        s/\#\s*/\#/;     # remove blanks between "#" and "include"
        ($keyword, $name, @dummy) = split /\s+/;

        if (/^\#include\s/ and $name) {
            $name =~ s/\"//g;
            $name =~ s/\'//g;
            $name =~ s/<//g;
            $name =~ s/>//g;
            #--print $name;
            $deps{$name} = 1;

        } elsif (/^[Ii][Nn][Cc][Ll][Uu][Dd][Ee]\s/ and $name) {
            $name =~ s/\"//g;
            $name =~ s/\'//g;
            #--print $name;
            $deps{$name} = 1;

        } elsif (/^[Uu][Ss][Ee]\s/ and $name) {
            $name =~ s/;//g;
            $name =~ s/,.*//gi;
            $name = fix_mod("$name");
            #--print $name;
            $deps{$name} = 1;

        } elsif (/^[Mm][Oo][Dd][Uu][Ll][Ee]\s/ and $name) {
            if (uc $name ne "PROCEDURE") {
                if ($mstring) {
                    #--------------------------------------
                    # if not the first module found in file
                    #--------------------------------------
                    print STDERR "fdp: extra module found in $srcfn: $name\n";
                    $mstring = $mstring . " ";
                }
                $mstring = $mstring . fix_mod("$name");
            }
        }
    }
    close INFILE;
}        

#=======================================================================
# name - write_output
# purpose - write the dependency file to disk or standard output
#=======================================================================
sub write_output {
    my $dep;

    # open output file
    #-----------------
    if ( $outfile ) {
        open(OUTFL, "> $outfile");
        print STDERR "Building dependency file $outfile\n" if $verbose;
    } else {
        open(OUTFL, ">& STDOUT");
    }

    # write to output file
    #---------------------
    print OUTFL "$base.d : $srcfn\n";
    if ($mstring) { print OUTFL "$mstring : $base.o\n" }
    print OUTFL "$base.o : $srcfn";
    foreach $dep ( keys %deps ) { 
        print OUTFL " $dep" unless ( $mstring =~ /\b$dep\b/ ); 
    }
    print OUTFL "\n";
    close OUTFL;
}

#=======================================================================
# name - fix_mod
# purpose - create modfile name with correct case for both root and
#           .mod extension
#=======================================================================
sub fix_mod {
    my ($name, $mod);
    $name = shift @_;

    if ($ncase eq "lower") { $name = lc $name; }
    else                   { $name = uc $name; }

    if ($mcase eq "lower") { $mod = "mod"; }
    else                   { $mod = "MOD"; }

    $name = "$name.$mod";
    return $name;
}

#=======================================================================
# name - usage
# purpose - print usage message
#=======================================================================
sub usage {

   print <<"EOF";

NAME
     $script - a simple depency generator for C or FORTRAN
          
SYNOPSIS

     $script OPTIONS file_name
          
DESCRIPTION

     Finds #include, include, and use kind of dependencies and prints them to
     stdout. Use this script for automatic dependence generation within
     the ESMA building mechanism. 

OPTIONS
     -c             Creates dependency file with extension .d instead of
                    writing to stdout.

     -i filename    Use filename for input file when creating rules;
                    This is useful when the filename is different than
                    the file_name input parameter, e.g. when the function
                    goes through /bin/cpp

     -o filename    Specifies name of output depency file name, default
                    is same as source file with .d extension.

     -p case.case   Because f90 does not specify the case of the
                    compiled module files, the user may need to specify
                    it with the -p option. For example, the simple f90
                    module
                        module fdp
                        end module fdp
                    may compile to fdp.mod, FDP.mod, fdp.MOD or FDP.MOD. 
                    You specify
                       -p fdp.mod   for the compilers producing fdp.mod
                       -p FDP.mod   for the compilers producing FDP.mod
                        etc.
                    The default is fdp.mod

     -v             verbose mode

BUGS
     It does not yet handle nested include files.

AUTHOR
     Arlindo da Silva, NASA/GSFC.

EOF

exit(1)
}