#! /usr/bin/perl -w
# -*-perl-*-
# fgen, makefile/dependencies generator (for GNU make) for fortran 77/90 code
# Copyright (C) 1997,98  Beroud Jean-Marc

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.    

# $Id: fgen.in,v 1.1 1998/03/22 21:54:18 ber Exp $

# need version >=5.000
require 5.000;

# modules
# use diagnostics;
use strict;
use Getopt::Long;
use Cwd;

# strip path from the calling program name
$0 =~ s/.*\///;

# force buffer flush
$| = 1;

#==============================================================================

# global vars
use vars qw(%dirs $fgenrc $make $depend $f77inc $nocheck $help);
use vars qw($fgen_datadir $copyright $calling_dir $date $incs);

use vars qw(%rcvars %db %vars %includes %targets %modules %files %library);

#==============================================================================

$fgenrc       = "_fgenrc_";
$fgen_datadir = "/opt/local/share/fgen";
$copyright    = "$0 v0.3 (C) 1997,98 Beroud Jean-Marc";
$calling_dir  = cwd();
$date         = `date`; chomp($date);

&parse_cmdline();

$rcvars{fc}{suffixes} |= ".h";

$incs = join("|", split(/\s+/, ".h $rcvars{fc}{suffixes}"));
$incs =~ s/\.//og;

 VARS:{
     my($type, $dir, $var);

     foreach $type (reverse keys %dirs) {
	 # reset counter
	 my $num = 0;

	 foreach $dir (@{ $dirs{$type} }) {
	     # build varname
	     $var = "$type";
		 
	     if ($#{ $dirs{$type} } > 0 && $num++ > 0) { $var .= "$num" }

	     # change dir and store absolute pathname
	     chdir("$dir") || die "$0: can't chdir to $dir: $!\n";

	     # store absolute pathdir
	     $dir = cwd();

	     # store varname (srcdir[.] or libdir[.])
	     $vars{$dir} = "$var";

	     # back to calling dir
	     chdir("$calling_dir") ||
		 die "$0: can't chdir to $calling_dir: $!\n";
	 }
     }
}
#make_alldirs() && exit;
 SCAN:{
     my($type, $dir);

     foreach $type (sort keys %dirs) {
	 foreach $dir (@{ $dirs{$type} }) {
	     print "scanning ", abs2rel($dir,$calling_dir), " ($vars{$dir})\n";

	     # change dir and store absolute pathname
	     chdir("$dir") || die "$0: can't chdir to $dir: $!\n";

	     &dep_scan($dir);

	     # back to calling dir
	     chdir("$calling_dir") ||
		 die "$0: can't chdir to $calling_dir: $!\n";
	 }
     }
 }

if ($make) {
    my $answer;

    print "\n";

    # ask user for targets names
    foreach (@{ $dirs{libdir} }) {
	print "Name of the library ($vars{$_}): ";

	$answer      = <STDIN>; chomp $answer;
	$library{$_} = $answer;
    }

    foreach (@{ $dirs{srcdir} }) {
	print "Name of the program ($vars{$_}): ";

	$answer      = <STDIN>; chomp $answer;
	$targets{$_} = $answer;
    }
}

 CREATE:{
     if ($make) {
	 print "\n"; &create_master($calling_dir);

	 my($type, $dir);

	 foreach $type (sort keys %dirs) {
	     foreach $dir (@{ $dirs{$type} }) {
		 if ($dir eq $calling_dir) {
		    print "$0: warning: Master makefile will be overwritten\n";
		 }
	     }
	 }
     }

     foreach (@{ $dirs{libdir} }, @{ $dirs{srcdir} }) {
	 print "\n";
	 &create_makefile($_) if $make;
	 &create_objsfile($_);
	 &create_depsfile($_);
     }

     foreach (@{ $dirs{incdir} }) {
	 print "\n"; &create_depsfile($_);
     }

     if ($make) {
	 print "\nMakefiles, dependencies and object lists generated\n";
	 print "Type `make help' for more informations\n";
     }
     else {
	 print "\nDependencies completed\n";
     }
 }

# shell return value
exit 0;

#==============================================================================

sub parse_cmdline {

    # parse command line switches

    &GetOptions(\%dirs      ,
                "incdir=s@" , # dirs: include files
                "libdir=s@" , # dirs: library files
                "srcdir=s@" , # dirs: source  files
		"m"         => \$make     , # make   mode
		"d"         => \$depend   , # depend mode
		"h"         => \$help     , # show help
		"n"         => \$nocheck  , # no utils check
                "77"        => \$f77inc   , # include files in f77 dialect
                "f=s"       => \$fgenrc   ) # alternate config file
	|| (&show_usage() && exit 1);
    
    # help
    if ($help) { &show_usage(); exit 0 }

    # make or depend mode is mandatory
    unless ($make || $depend) {
	print "$0: mode not specified. Try `$0 -h' for more information.\n";
	exit 1;
    }

    # test the avilability of the config file
    if ("$fgenrc" eq "_fgenrc_") {
	$fgenrc = "fgenrc";

	if (-e "$ENV{HOME}/.$fgenrc") {
	    $fgenrc = "$ENV{HOME}/.$fgenrc";
	}
	elsif (-e "$fgen_datadir/$fgenrc") {
	    $fgenrc = "$fgen_datadir/$fgenrc";
	}
	else {
	    print "$0: no config file found\n";
	    print "please take one from the directory $fgen_datadir\n";
	    exit 2
	}
    }
    elsif (! -e "$fgenrc") {
	print "$0: no such config file: ($fgenrc)\n";
	exit 2;
    }

    # parse config file
    &parse_rcfile($fgenrc);

    # if none dir specified 
    $dirs{srcdir} = [ $calling_dir ] unless (keys %dirs);
}

#==============================================================================

sub show_usage {

    # print usage on stdout if called without arguments

    print <<EOF;
$copyright
$0 -m|-d [-h] [-n] [-77] [-f fgenrc] [-i dir [-i dir] ...]
     [-l dir [-l dir] ...] [-s dir [-s dir] ...]

-m       : generate makefile+dependencies
-d       : generate dependencies only
-n       : no checking for makefile utilities
-h       : show this page
-77      : header files (*.h ...) are in f77 (default is f90)
-f fgenrc: specify an alternate config file for $0

-s dir   : source directory. Target is an executable )
-l dir   : lib    directory. Target is a library     > (default is -s ./)
-i dir   : header directory. No target               )

The name of a library should be of type: lib*.a

$0 scans for *.F90 *.f90 *.F *.f *.h *.c files + user defined file extensions
EOF
}

#==============================================================================

sub parse_rcfile() {

    # open and parse user configuration file.

    my($file) = @_;

    # slurp file into memory
    open (RCFILE, "$file") || die "cannot open $file:$!\n";

    my @file_content = <RCFILE>; close RCFILE;

    # parse file
    my($part, $key, $value);

    foreach (@file_content) {
	# skip those
	next if /^\s*(\#|$)/;

	chomp;

	# trim white spaces
	s/^\s+|\s+$//og;

	if (/^\[(.*)\]$/) {
	    $part = $1;
	}
	else {
	    ($key, $value) = split(/\s*=\s*/, $_, 2);

	    # store peers
	    $rcvars{$part}{$key} = $value;
	}
    }
}

#==============================================================================

sub which {

    # search execs in $PATH (different names for a same program e.g:
    # gcc, cc, lcc) passed as arguments. Return the first occurence found.
    # If not successfull, return empty

    # arguments
    my @bins = @_;

    # return if arguments are empty
    return undef if ($#bins == 0 && $bins[0] eq "");

    # return if no check wanted
    return $bins[0] if ($nocheck);

    # local variables
    my @paths = split (":", $ENV{PATH});

    my($path, $bin);

    foreach $bin (@bins) {
	# absolute path
	if ($bin =~ /^\s*\//) {
	    if (-e "$bin" && -x "$bin" && ! -d "$bin") {
		return "$bin";
	    }
	}
	# relative/without path 
	else {
	    foreach $path (@paths) {
		if (-e "$path/$bin"  && -x "$path/$bin" &&
		    ! -d "$path/$bin") {
		    # return "$path/$bin";
		    return "$bin";
		}
	    }
	}
    }

    # none found
    return undef;
}

#==============================================================================

sub dep_scan {

    # argument
    my($dir) = @_;

    my $file;

    my $exts = "\*\." . join(" \*\.", split(/\|/, $incs));

    # scan directory
    foreach $file (glob "*.[cfFh] *.f90 *.F90 $exts") {
	my(@includes, @uses, @modules);
	my($f77, $f90) = (0, 0);

	# open file for reading
	open (FILE, "$file") || die "$0: can't open $file: $!\n";

	# slurp file in memory
	my @file_content = <FILE>; close FILE;

	if    ($file =~ /\.(f|F)$/o) {
	    $f77 = 1;
	}
	elsif ($file =~ /\.(f|F)90$/o) {
	    $f90 = 1;
	}
	elsif ($file =~ /\.($incs)$/o) {
	    $f77inc ? $f77 = 1 : $f90 = 1;
	}

	if ($f77 || $f90) {
	    foreach (@file_content) {
		# skip those
		next if /^\s*(!|$)/o;

		# comment lines (fixed format)
	        next if ($f77 && /^[^\d^\s]/o);

		# assume: one include statement, at the beginning of a line
		# I think this is defined in the standard
		if (/^(?:\#|\s*)include\s*(?:\'(.*?)\'|\"(.*?)\")/oi) {
		    push(@includes, $1 || $2); next;
		}
		
		if (/^\s*module\s+(\w+)/oi && $1 !~ /procedure/oi) {
		    push(@modules, $1); next;
		}

		if (/\buse\b/oi) {
		    # remove inline comments to be sure
		    if (/!/o) { $_ = skip_inline_comments($_) }

		    push(@uses, /\buse\s+(\w+)/oig);
		}
	    }
	}
	else {
	    # c files (only #include "*" not #include <*>)
	    # don't take care of comments now
	    foreach (@file_content) {
		if (/^\#include\s*(?:\'(.*?)\'|\"(.*?)\")/o) {
		    push(@includes, $1 || $2);
		}
	    }
	}

	# store informations for each file
	$db{$dir}{$file}{includes} = [ &uniq  (@includes) ];
	$db{$dir}{$file}{uses    } = [ &uniqtr(@uses    ) ];
	$db{$dir}{$file}{modules } = [ @modules = &uniqtr(@modules) ];

	foreach (@modules) { $modules{$_} = $file }

	if ($file =~ /\.($incs)$/) { $includes{$file} = $file }

	$files{$file} = $vars{$dir};
    }
}

#==============================================================================

sub skip_inline_comments {

    # argument
    my($code) = @_;

    if ($code =~ /\'|\"/o) {
	my @chunks = split(/(\".*?\"|\'.*?\'|\s*!.*$)/o, $code);

	pop @chunks if ($chunks[-1] =~ /^!/o);

	$code = join("", @chunks);
    }
    else {
	$code =~ s/!.*$//o;
    }

    return $code;
}

#==============================================================================

sub uniqtr {

    # return only uniq keywords

    # argument
    my @list = @_;

    my %uniqs;

    foreach (@list) {
	tr [A-Z] [a-z];
	$uniqs{$_} = undef;
    }

    return sort keys %uniqs;
}

#==============================================================================

sub uniq {

    # return only uniq keywords

    # argument
    my @list = @_;

    my %uniqs;

    foreach (@list) { $uniqs{$_} = undef }

    return sort keys %uniqs;
}

#==============================================================================

sub create_depsfile
{
    # create dependency file

    # argument
    my($dir) = @_;

    print "creating depsfile \`$rcvars{make}{depsfile}' ($vars{$dir})\n";

    my $depsfile = "$dir/$rcvars{make}{depsfile}";

    # create dependency file
    open(DEPS, ">$depsfile") || die "can't open $depsfile: $!\n";

    # include dirs
    my $include = "";

    if ($vars{$dir} !~ /incdir/) {
	foreach (@{ $dirs{incdir} }) {
	    $include .= "include \$($vars{$_})/$rcvars{make}{depsfile}\n";
	}
    }

    print DEPS <<EOF;
# Automatically generated dependencies. $date
# $copyright

$include
EOF

    my(%local_modules, @local_includes, $file, $obj);

    # locals (possible symlinks)
    foreach $file (keys %{ $db{$dir} }) {
	foreach (@{ $db{$dir}{$file}->{modules} }) {
	    $local_modules{$_} = $file;
	}

	push(@local_includes, $file) if ($file =~ /\.($incs)$/o);
    }

    foreach $file (sort keys %{ $db{$dir} }) {
	($obj = $file) =~ s/\.\w+$/\.o/o;

	# header file
	if ($file =~ /\.($incs)$/o) {
	    print DEPS "$file: \\\n\t";
	}
	# source file
	else {
	    print DEPS "\$($vars{$dir})/$obj $obj:",
	               " \\\n\t\$($vars{$dir})/$file";
	}

	# use loop
	my $use;

      USE:
	foreach $use (@{ $db{$dir}{$file}->{uses} }) {
	    foreach (keys %local_modules) {
		if (/$use/) {
                    # skip if <module foo> and <use foo> are in the same file
                    if ($local_modules{$use} ne $file) {
			($obj = $local_modules{$use}) =~ s/\.\w+$/\.o/o;

			print DEPS " \\\n\t\$($vars{$dir})/$obj";

			next USE;
		    }

		    next USE;
		}
	    }

	    foreach (keys %modules) {
		if (/$use/) {
                    # skip if <module foo> and <use foo> are in the same file
                    if ($modules{$use} ne $file) {
		        ($obj = $modules{$use}) =~ s/\.\w+$/\.o/o;

		        print DEPS " \\\n\t\$($files{$modules{$use}})/$obj";

		        next USE;
                    }

		    next USE;
		}
	    }
	    # failed
	    print "$0: warning: missing module $use ($file)\n";
	}

	# include loop
	my $include;

      INCLUDE:
	foreach $include (@{ $db{$dir}{$file}->{includes} }) {
	    foreach (@local_includes) {
		if (/$include/) {
		    print DEPS " \\\n\t\$($vars{$dir})/$_";
		    
		    next INCLUDE;
		}
	    }

	    foreach (keys %includes) {
		if (/$include/) {
		    print DEPS " \\\n\t\$($files{$_})/$_";
		    
		    next INCLUDE;
		}
	    }

	    # failed
	    print "$0: warning: missing include file $include ($file)\n";
	}

	print DEPS "\n\n";
    }

    close DEPS;
}

#==============================================================================

sub create_objsfile {

    # create object file

    # arguments
    my($dir) = @_;

    my $objsfile = "$dir/$rcvars{make}{objsfile}";

    print "creating objsfile \`$rcvars{make}{objsfile}' ($vars{$dir})\n";

    # create objsfile
    open(OBJS, ">$objsfile") || die "$0: can't open $objsfile: $!\n";

    print OBJS "# Automatically generated objects. $date\n";
    print OBJS "# $copyright\n\n";

    print OBJS "OBJS =";

    my $obj;

    foreach (sort keys %{ $db{$dir} }) {
	# skip header files
	next if (/\.($incs)$/);

	($obj = $_) =~ s/\.\w+$/\.o/;

	print OBJS " \\\n\t\$($files{$_})/$obj";
    }

    close OBJS;
}

#==============================================================================

sub create_master {

    # argument
    my($dir) = @_;

    my $makefile = "$dir/$rcvars{make}{makefile}";

    print "creating Master makefile \`$rcvars{make}{makefile}' (",
          abs2rel($dir, $calling_dir), ")\n";

    my $master   = make_master();
    my $utils    = make_utils();
    my $alldirs  = make_alldirs($calling_dir);
    my $args     = make_args($dir);
    my $dep      = make_dep();
    my $html     = make_html();
    my $help     = make_help();

    # targets
    my($all, $prjdirs, $targets) = ("", "", "");

    foreach (values %library, values %targets) { $all .= "$_ " }
    foreach (@{ $dirs{libdir} }, @{ $dirs{srcdir} }) {
        $prjdirs .= "\$($vars{$_}) ";
    }

    foreach (keys %library) {
	my $lib = $library{$_};
	my $dir = "\$($vars{$_})";
	$targets .= "$lib:\n\t\@\$(CD) $dir && \\\n".
	            "\t    \$(MAKE) -f \$(MAKEFILE) \$\@\n\n";
    }

    foreach (keys %targets) {
        my $bin = $targets{$_};
        my $dir = "\$($vars{$_})";
        $targets .= "$bin:\n\t\@\$(CD) $dir && \\\n".
	            "\t    \$(MAKE) -f \$(MAKEFILE) \$\@\n\n";
    }

    # create Makefile
    open(MAKE, ">$makefile") || die "$0: can't open $makefile: $!\n";

    print MAKE <<EOF;
# Automatically generated GNU Makefile. $date
# $copyright

$master
$utils
$alldirs
PRJDIRS = $prjdirs

# Not real file targets
.PHONY: \$(MAKEFILE) all dep html clean install help

# targets
all: $all

$targets
$args
$dep
$html
# cleanup
clean:
\t\@for dir in \$(PRJDIRS); do \\
\t    (\$(CD) \$\$dir && \$(MAKE) -f \$(MAKEFILE) \$\@); \\
\t done

# installation
install:
\t\@for dir in \$(PRJDIRS); do \\
\t    (\$(CD) \$\$dir && \$(MAKE) -f \$(MAKEFILE) \$\@); \\
\t done

$help
EOF
}

#==============================================================================

sub create_makefile {

    #create a makefile depending on $arch

    # argument
    my($dir) = @_;

    my $makefile = "$dir/$rcvars{make}{makefile}";

    print "creating makefile \`$rcvars{make}{makefile}' ($vars{$dir})\n";

    my $fpp = &which($rcvars{fpp}{fpp}) || &notdef('FPP');
    my $cc  = &which($rcvars{cc}{cc}) || &notdef('CC');
    my $fc  = &which($rcvars{fc}{fc}) || &notdef('FC');
    my $ld  = &which($rcvars{ld}{ld}) || &notdef('LD');
    my $ar  = &which('ar') || &notdef('AR');

    $targets{$dir} ||= "";
    $library{$dir} ||= "";

    my $standard = make_standard();
    my $utils    = make_utils();
    my $alldirs  = make_alldirs($dir);
    my $args     = make_args($dir);
    my $dep      = make_dep();
    my $html     = make_html();
    my $help     = make_help();

    # headers
    my $incdirs = "";

    foreach (@{ $dirs{incdir} }) { $incdirs .= "-I\$($vars{$_}) " }

    # libraries
    my($libdirs, $libs, $libdep) = ("", "", "");

    foreach (@{ $dirs{libdir} }) {
        my $lib = $library{$_}; $lib =~ s/^\s*lib(.*)\.a\s*$/$1/;

        $libdirs .= "-L\$($vars{$_}) ";
        $libs    .= "-l$lib ";

	if ($vars{$dir} =~ /srcdir/) {
	    $libdep .= "\$($vars{$_})/$library{$_} "; 
	}
    }

    # cray f90 stuff
    my $cray = "";

    if ($rcvars{fc}{fc} =~ /f90/i && $rcvars{fc}{vendor} =~ /cray/i) {
        $cray .= "\n# paths for modules (cray f90 compiler)\nFFLAGS  += ";
        foreach (@{ $dirs{libdir} }) { $cray .= "-p\$($vars{$_}) " }
        $cray .= "\n";
    }

    # create Makefile
    open(MAKE, ">$makefile") || die "$0: can't open $makefile: $!\n";
    
    print MAKE <<EOF;
# Automatically generated GNU Makefile. $date
# $copyright

$standard
$utils
$alldirs
# preprocessor, compilers, linker & archiver
FPP = $fpp
CC  = $cc
FC  = $fc
LD  = $ld
AR  = $ar

# default mode (max. optimization)
mode = opt

# header file directories
CCFLAGS = $incdirs
FFLAGS  = $incdirs

# default flags
FPPFLAGS = $rcvars{fpp}{fppflags}
CCFLAGS += $rcvars{cc}{ccflags}
FFLAGS  += $rcvars{fc}{fflags}
LDFLAGS  = $rcvars{ld}{ldflags}
ARFLAGS  = $rcvars{ar}{arflags}

# add flags for debugging if requested
ifeq (dbg,\$(findstring dbg,\$(mode)))
   CCFLAGS += $rcvars{cc}{dbg}
   FFLAGS  += $rcvars{fc}{dbg}
   LDFLAGS += $rcvars{ld}{dbg}
endif

# add flags for profiling if requested
ifeq (pro,\$(findstring pro,\$(mode)))
   CCFLAGS += $rcvars{cc}{pro}
   FFLAGS  += $rcvars{fc}{pro}
   LDFLAGS += $rcvars{ld}{pro}
endif

# add flags for optimization if requested
ifeq (opt,\$(findstring opt,\$(mode)))
   CCFLAGS += $rcvars{cc}{opt}
   FFLAGS  += $rcvars{fc}{opt}
   LDFLAGS += $rcvars{ld}{opt}
endif
$cray
# objectlist file
include $rcvars{make}{objsfile}

# additionnal libraries
LIBDIRS = $libdirs $rcvars{ld}{libdirs}
LIBS    = $libs $rcvars{ld}{libs}

# target names
BIN = $targets{$dir}
LIB = $library{$dir}

# Not real file targets
.PHONY: \$(MAKEFILE) $rcvars{make}{depsfile} $rcvars{make}{objsfile} \\
        all dep html clean install help

# targets
all: \$(LIB) \$(BIN)

\$(LIB): \$(OBJS)
\t\@\$(ECHO) ""
\t\@\$(ECHO) "Creating archive \$(\@F)"
\t\@\$(ECHO) ""
\t\$(AR) \$(ARFLAGS) \$\@ \$(notdir \$(OBJS))


\$(BIN): \$(OBJS) $libdep
\t\@\$(ECHO) ""
\t\@\$(ECHO) "Linking executable \$(\@F)"
\t\@\$(ECHO) ""
\t\$(LD) \$(LDFLAGS) \$(notdir \$(OBJS)) \$(LIBDIRS) \$(LIBS) -o \$\@

$args
$dep
$html
# cleanup
clean:
\t\$(RM) -f \$(BIN) \$(LIB) \$(notdir \$(OBJS)) *.[dlMT] *.lst *.mod work.pc* core

# installation
LIBDIR = $rcvars{make}{libdir}
BINDIR = $rcvars{make}{bindir}

install:
\t\if [ -n "\$(LIB)" ] && [ -r "\$(LIB)" ]; then \\
\t   \$(INSTALL) -m 644 \$(LIB) \$(LIBDIR); \\
\tfi
\t\if [ -n "\$(BIN)" ] && [ -r "\$(BIN)" ]; then \\
\t   \$(INSTALL) -c \$(BIN) \$(BINDIR); \\
\tfi

# suffixes
.SUFFIXES:
.SUFFIXES: $rcvars{fc}{suffixes} .F .f .F90 .f90 .c .o

# remove target on error
.DELETE_ON_ERROR:

# implicit rules
# Want full path? Change \$(<F) to \$< and add -o \$(\@F) or -o \$\@
\%.o: \%.F   ; \$(FC) -c \$(FFLAGS)  \$(<F)
\%.o: \%.f   ; \$(FC) -c \$(FFLAGS)  \$(<F)
\%.o: \%.F90 ; \$(FC) -c \$(FFLAGS)  \$(<F)
\%.o: \%.f90 ; \$(FC) -c \$(FFLAGS)  \$(<F)
\%.o: \%.c   ; \$(CC) -c \$(CCFLAGS) \$(<F)

# if the compiler do no support the F90 extension
#\%.o: \%.F90
#\t\$(MV) \$(<F) \$(*F).c
#\t\$(FPP) \$(FPPFLAGS) \$(*F).c > \$(*F)-tmp.f90
#\t\$(FC) -c \$(FFLAGS) \$(INCDIRS) \$(*F)-tmp.f90
#\t\$(MV) \$(*F)-tmp.o \$(*F).o
#\t\$(RM) -f \$(*F).c \$(*F)-tmp.f90 

$help
# dependencies file
include $rcvars{make}{depsfile}
EOF

    close MAKE;
}

#==============================================================================

sub make_standard {
    my $shell = &which('sh', 'ksh', 'bash') || &notdef('SHELL');
    my $make  = &which('gmake', 'make') || &notdef('MAKE');
    
    return <<EOF;
# simultaneous parallel jobs & load average limit
MAXJOBS = $rcvars{make}{maxjobs}
MAXLOAD = $rcvars{make}{maxload}

# standard
SHELL     = $shell
MAKE      = $make
MAKEFILE  = $rcvars{make}{makefile}
MAKEFLAGS = -r -j\$(MAXJOBS) -l\$(MAXLOAD)
EOF
}

#==============================================================================

sub make_master {
    my $shell = &which('sh', 'ksh', 'bash') || &notdef('SHELL');
    my $make  = &which('gmake', 'make') || &notdef('MAKE');
    
    return <<EOF;
# standard
SHELL     = $shell
MAKE      = $make
MAKEFILE  = $rcvars{make}{makefile}
MAKEFLAGS = -r
EOF
}

#==============================================================================

sub make_utils {
    my $fgen    = &which($0) || &notdef('FGEN');
    my $f2html  = &which('f2html') || &notdef('F2HTML');
    my $echo    = &which('echo') || &notdef('ECHO');
    my $rm      = &which('rm') || &notdef('RM');
    my $cp      = &which('cp') || &notdef('CP');
    my $mv      = &which('mv') || &notdef('MV');
    my $mkdir   = &which('mkdir')  || &notdef('MKDIR');
    my $tar     = &which('gtar', 'tar') || &notdef('TAR');
    my $gzip    = &which('gzip', 'compress') || &notdef('GZIP');
    my $install = &which('install-sh', 'install') || &notdef('INSTALL');

    return <<EOF;
# utils
FGEN    = $fgen
F2HTML  = $f2html
ECHO    = $echo
RM      = $rm
CP      = $cp
MV      = $mv
CD      = cd
MKDIR   = $mkdir
TAR     = $tar
GZIP    = $gzip
INSTALL = $install
EOF
}

#==============================================================================

sub make_alldirs {
  # argument
  my($dir) = @_;

    my $alldirs = "# dirs\n";
    my $key;

    foreach $key (sort keys %dirs) {
        foreach (@{ $dirs{$key} }) {
	  $alldirs .= "$vars{$_} = " . &abs2rel($_, $dir) . "\n";
	}
    }
    return $alldirs;
}

#==============================================================================

sub make_args {
    # arguments
    my($dir) = @_;

    my($fgen_args, $f2html_args) = ("", "");

    if ("$fgenrc" ne "$ENV{HOME}/.fgenrc" && 
	"$fgenrc" ne "$fgen_datadir/fgenrc") {

	my $file = $fgenrc;

	if ($file !~ /^\//o) {
	    my $filedir;

	    if ($file !~ /\//o) { $file = "$calling_dir/$file" }

	    $file =~ /(.*\/)(.*)/;
	    ($filedir, $file) = ($1, $2);

	    # change dir and store absolute pathname
	    chdir("$filedir") || die "$0: can't chdir to $dir: $!\n";
	    
	    $filedir = cwd();

	    # back to calling dir
	    chdir("$calling_dir") ||
		die "$0: can't chdir to $calling_dir: $!\n";

	    $filedir = abs2rel($filedir, $dir);
	    $file    = "$filedir/$file";
	    $file    =~ s/\/+/\//go;
	}

	$fgen_args   .= "-f $file ";
	$f2html_args .= "-f $file ";
    }

    if ($f77inc) {
	$fgen_args   .= "-77 ";
	$f2html_args .= "-77 ";
    }

    foreach (@{ $dirs{srcdir} }) {
	$fgen_args   .= "-s \$($vars{$_}) ";
	$f2html_args .= "\$($vars{$_}) ";
    }

    foreach (@{ $dirs{libdir} }) {
	$fgen_args   .= "-l \$($vars{$_}) ";
	$f2html_args .= "\$($vars{$_}) ";
    }

    foreach (@{ $dirs{incdir} }) {
	$fgen_args   .= "-i \$($vars{$_}) ";
	$f2html_args .= "\$($vars{$_}) ";
    }

    return <<EOF;
# args for fgen & f2html
FGENARGS   = $fgen_args
F2HTMLARGS = $f2html_args
EOF
}

#==============================================================================

sub make_dep {
    return <<EOF;
# rebuild dependencies
dep:
\t\@\$(FGEN) -d \$(FGENARGS)
EOF
}

#==============================================================================

sub make_html {
    return <<EOF
# build html pages
HTMLDIR = $rcvars{html}{dir}

html:
\t\@\$(F2HTML) -d \$(HTMLDIR) \$(F2HTMLARGS) 
EOF
}

#==============================================================================

sub make_help {
    return <<EOF;
# help page
help:
\t\@\$(ECHO) "Defined targets:"
\t\@\$(ECHO) "  all    : build targets (default)"
\t\@\$(ECHO) "  dep    : build dependencies"
\t\@\$(ECHO) "  html   : build html pages"
\t\@\$(ECHO) "  clean  : cleanup"
\t\@\$(ECHO) "  install: install executable"
\t\@\$(ECHO) "Defined modes:"
\t\@\$(ECHO) "  opt: enable flags for optimization (default)"
\t\@\$(ECHO) "  dbg: enable flags for debugging"
\t\@\$(ECHO) "  pro: enable flags for profiling"
\t\@\$(ECHO) "Example:"
\t\@\$(ECHO) "  type \\`make mode=dbg+pro' to enable dbg and pro flags"
EOF
}

#==============================================================================

sub notdef {

    # argument
    my($var) = @_;

    print "$0: warning: variable $var is not defined in the makefile\n";

    return "# not defined";
}

#==============================================================================

sub abs2rel {
    #arguments
    my($path, $base) = @_;

    my(@parts, @common, $com, $i);

    # remove leading slash
    $path =~ s/^\///;
    $base =~ s/^\///;

    # common parts
    @parts = split("/", $path);

    foreach (split("/", $base)) {
	if ($_ eq $parts[0]) {
	    push(@common, $_); shift(@parts); last if (! @parts);
	}
	else { last }
    }

    $com  = join("/", @common);
    $path =~ s/$com[\/]?//;

    if ($base eq $com) {
	return "./$path";
    }
    else {
	$base   =~ s/^[\/]?$com//;
	@common = split("/", $base);

	for($i=0;$i<$#common;$i++) { $path = "../$path" }
	return "$path";
    }
}

__END__

# man page
# to create it on a linux system:
# $> pod2man fgen |groff -Tascii -mandoc

=head1 NAME

fgen - Makefile generator (for GNU make) for fortran 77/90 code

=head1 SYNOPSIS

fgen -B<m>|-B<d> [-B<h>] [-B<77>] [-B<c>][-B<f> fgenrc] [-B<i> dir
[-B<i> dir] ...]
     [-B<l> dir [-B<l> dir] ...] [-B<s> dir [-B<s> dir] ...]

=head1 DESCRIPTION

Generate makefiles and dependencies for fortran 77/90 code without hassle.

=head1 OPTIONS

=over 12

=item -B<m>

Generate Makefiles. These Makefiles are I<only> targeted for the GNU Make
program. Make other than GNU Make will B<NOT> be able to read the Makefiles
produced by fgen without errors. You have been warned! Dependencies are
automatically generated when creating makefiles.

=item -B<d>

Generate dependencies. Two files are generated: dependencies and objectlist
file. The first contains all file dependencies, and the second the definition
of the OBJS macro which list all objects. Dependencies are also generated by
calling 'make dep' once you have created the makefiles.

=item -B<77>

This switch tells fgen that B<all> header files are in fortran 77. This seems
necessary, because there is no way to be sure if a header file is in fortran 77
or in fortran 90

=item -B<h>

Show usage and exit

=item -B<c>

Do not check that the programms used in the makefiles are in the PATH. This is
useful for buiding a makefile on a different machine than the one used to run
make.

=item -B<f> F<fgenrc>

F<fgenrc> is an alternate configuration file for fgen. If this option is not
specified, fgen search in this order: a user specific configuration file
F<~/.fgenrc>, then the system default configuration file
F</opt/local/share/fgen/fgenrc>. If this fails, that means that your installation
is broken and fgen exit with an error message

=item -B<s> srcdir -B<l> libdir -B<i> incdir

List of the search directories. If none is specified, the current working
directory is taken as a srcdir (the same as if -B<s> ./ was specified).

srcdir: an executable is assumed as the target

libdir: a library is assumed as target

incdir: optionnaly contains header files (*.h ...)

A single dependencies file is generated in incdirs. The script assume that
there is only include files living in incdirs.

There is no limit for the number of search directories.

=back

=head1 FILES

~/.fgenrc

/opt/local/share/fgen/fgenrc

=head1 AUTHOR

Beroud Jean-Marc (ber@sma.ch). Bug reports and suggestions are welcome!

=cut
