2025-09-05 09:42:21 +02:00

788 lines
22 KiB
Perl
Executable File

#!/usr/bin/env perl
#
# svn $Id: sfmakedepend 751 2015-01-07 22:56:36Z arango $
#######################################################################
# Copyright (c) 2002-2015 The ROMS/TOMS Group #
# Licensed under a MIT/X style license #
# See License_ROMS.txt #
##################################################### Kate Hedstrom ###
# #
# Fortran 90/77 dependency checker, version 2007b. #
# #
#######################################################################
use 5.6.0;
use strict;
use File::Basename;
use Getopt::Long;
use Pod::Usage;
use Getopt::Long;
use File::Copy;
my ($opt_help, $opt_man, $opt_file, @incdirs, @srcdirs, @defines,
$opt_modext, $opt_case, $compiler, $opt_depend, $drop_circ);
our ($cpp, $add_ext, $mod_dir, $libdeps, $longpath, $obj_dir, %defines,
$silent);
our $obj_ext = 'o';
our $ext = 'f';
# Parse the arguments, do the right thing for --help, --man.
Getopt::Long::Configure( "bundling" );
GetOptions("help" => \$opt_help, "man" => \$opt_man,
"file=s" => \$opt_file, "I=s@" => \@incdirs,
"srcdir=s@" => \@srcdirs, "moddir=s" => \$mod_dir,
"fext=s" => \$ext, "objext=s" => \$obj_ext,
"modext=s" => \$opt_modext, "addext=s" => \$add_ext,
"case=s" => \$opt_case, "compiler=s" => \$compiler,
"depend=s" => \$opt_depend, "cpp" => \$cpp,
"libdeps" => \$libdeps, "drop" => \$drop_circ,
"longpath" => \$longpath, "objdir=s" => \$obj_dir,
"D=s@" => \@defines, "silent" => \$silent )
or pod2usage("Try '$0 --help' for more information");
pod2usage(-verbose => 1) if $opt_help;
pod2usage(-verbose => 2) if $opt_man;
our @suffixes = qw( .c .C .cc .cxx .cpp .f .F .f90 .F90 .f95 .F95 .for);
our @mod_files = ();
my $mf = 'Makefile';
if ($opt_file) {
$mf = $opt_file;
} elsif (-f "makefile") {
$mf = 'makefile';
}
if ( !(-f $mf) && ($mf ne '-')) {
system "touch $mf";
}
if (@defines) {
my $foo;
foreach $foo (@defines) {
my($before, $after) = split /=/, $foo;
$defines{$before} = $after;
}
}
# extension used for compiler's private module information
our $mod_ext = "mod";
our $depend = "obj";
our $case = "lower";
our $obj_dep_flag;
my $ll = 79; # maximum line length in Makefile
my $cray;
my $parasoft;
my $nothing = "\n";
# Check the compiler first, then override the compiler-specific defaults
if ($compiler) {
if ($compiler eq "crayold") {
$cray = 1;
$case = "lower";
$depend = "obj";
$obj_dep_flag = "-p";
} elsif ($compiler eq "cray") {
$case = "upper";
} elsif ($compiler eq "parasoft") {
$parasoft = 1;
$case = "lower";
$depend = "obj";
$obj_dep_flag = "-module";
} elsif ($compiler eq "sgiold") {
$mod_ext = "kmo";
$case = "upper";
} elsif ($compiler eq "sgi" or $compiler eq "hp" or
$compiler eq "absoft") {
$case = "upper";
} elsif ($compiler eq "nag" or $compiler eq "ibm" or
$compiler eq "sun") {
$case = "lower";
} else {
warn "Unknown compiler: $compiler\n";
}
}
$depend = $opt_depend if defined($opt_depend);
if ($depend eq "obj") {
$drop_circ = 1;
}
$case = $opt_case if defined($opt_case);
# extension used for compiler's private module information
if ($opt_modext) {
$mod_ext = $opt_modext;
}
# need to add some more dependencies so the .f file gets created
our $need_f;
if ($cpp and $depend eq "obj") {
$need_f = 1;
}
my $mystring = '# DO NOT DELETE THIS LINE - used by make depend';
# Search for the includes in all the files
my $file;
my %sources;
foreach $file (@ARGV) {
my $filesrc = findsrc($file);
$sources{$file} = new Source_File($file, $filesrc, $filesrc);
$sources{$file}->find_includes();
}
# Create new Makefile with new dependencies.
if ($mf ne "-") {
copy( "$mf", "$mf.old");
open(MFILE, "$mf.old") || die "can't read Makefile $mf.old: $!\n";
open(NMFILE, "> $mf") || die "can't write $mf: $!\n";
select(NMFILE);
while (<MFILE>) {
if (!/$mystring/) {
print;
} else {
last;
}
}
print $mystring, "\n";
}
# Now print out include and use dependencies in sorted order.
my $target;
foreach $target (sort keys(%sources)) {
$sources{$target}->print();
# Hernan hack
print $nothing;
}
# print out module dependencies
if ( !( $cray || $parasoft) ) {
my $modname;
foreach $modname (sort keys(%main::mod_files)) {
my ($name, $path, $suffix) = fileparse(
$sources{$main::mod_files{$modname}}->{'filepath'}, @suffixes);
my $object = $name . "." . $obj_ext;
if (!( $drop_circ && lc($modname) eq lc($name)) ) {
$object =~ s#^\./##;
my $modfile = "$modname.$mod_ext";
my $objfile = $object;
$modfile = $mod_dir . '/' . $modfile if $mod_dir;
$objfile = $obj_dir . '/' . $objfile if $obj_dir;
print "$modfile: $objfile\n";
}
}
}
#
# End of main
#
sub findfile {
# Let's see if we can find the included file. Look in current
# directory first, then in directories from -I arguments.
my $file = shift;
my ($found, $i, $filepath);
$found = 0;
if ( -f $file ) {
$found = 1;
$file =~ s#^\./##; # convert ./foo.h to foo.h
return $file;
}
foreach $i (0 .. $#incdirs) {
$filepath = $incdirs[$i]."/".$file;
if ( -f $filepath ) {
$found = 1;
$filepath =~ s#^\./##; # convert ./foo.h to foo.h
return $filepath;
}
}
if ( ! $found ) {
$filepath = "";
}
$filepath;
}
#-----------------------------------------------------------------------
sub findsrc {
# Let's see if we can find the source-file. Look in current
# directory first, then in directories from --srcdir arguments.
my $src = shift;
my($found, $i, $srcpath);
$found = 0;
if ( -f $src ) {
$found = 1;
$src =~ s#^\./##; # convert ./foo.h to foo.h
return $src;
}
foreach $i (0 .. $#srcdirs) {
$srcpath = $srcdirs[$i]."/".$src;
if ( -f $srcpath ) {
$found = 1;
$srcpath =~ s#^\./##; # convert ./foo.h to foo.h
return $srcpath;
}
}
if ( ! $found ) {
$srcpath = "";
}
$srcpath;
}
#################################################################
package Source_File;
# hash containing names of included files
my %inc_files = ();
my %flist;
# Constructor
sub new {
my $type = shift;
my $filename = shift;
my $path = shift;
my $parent = shift;
my $self = {};
$self->{'Source_File'} = $filename;
$self->{'filepath'} = $path;
$self->{'parent'} = $parent;
$self->{'includes'} = {};
$self->{'uses'} = {};
$self->{'modules'} = {};
bless $self;
}
sub find_includes {
my $self = shift;
my $file = $self->{'filepath'};
my $parent = $self->{'parent'};
my($after, $filepath, $ref, $included, $use, $modname);
local(*FILE);
local($_);
if (-f $file) {
open(FILE, $file) || $silent || warn "Can't open $file: $!\n";
} else {
return;
}
while (<FILE>) {
$included = "";
$use = "";
# look for Fortran style includes
if (/^\s*include\s*['"]([^"']*)["']/i) {
$included = $1;
$after = $';
# C preprocessor style includes
} elsif (/^#\s*include\s*["<]([^">]*)[">]/) {
$included = $1;
$after = $';
# Fortran 90 "use"
} elsif (/^\s*use\s+(\w+)/i) {
# Gavin Salam attempt at dealing with multiple uses on one line
# remove trailing comments (do not take uses after a comment!)
s/\!.*//;
# try and get multiple use commands
my @commands = split(';',$_);
while (my $command = shift @commands) {
if ($command =~ /^\s*use\s+(\w+)/i) {
$use = $1;
# Change the case as needed - compiler dependent.
if ($main::case eq "upper") {
$use = uc($use);
} elsif ($main::case eq "lower") {
$use = lc($use);
}
$self->{'uses'}{$use} = 1;
}
}
# Fortran 90 module
} elsif (/^\s*module\s+(\w+)/i) {
$modname = $1;
if ($main::case eq "upper") {
$modname = uc($modname);
} elsif ($main::case eq "lower") {
$modname = lc($modname);
}
# Skip "module procedure" in interface blocks
next if (lc($modname) eq "procedure");
$main::mod_files{$modname} = $parent;
$self->{'modules'}{$modname} = 1;
}
# C preprocessor style includes of a -DROMS_HEADER sort
my $key;
foreach $key (keys(%defines)) {
if (/^#\s*include\s*($key)/) {
$included = $defines{$key};
$after = $';
}
}
if ($included) {
# See if we've already searched this file
if ( $inc_files{$included} ) {
$filepath = $inc_files{$included}{'filepath'};
} else {
$filepath = main::findfile($included);
$ref = new Source_File($included, $filepath, $parent);
$inc_files{$included} = $ref;
# Search included file for includes
$ref->find_includes();
}
if ( $filepath ) {
$self->{'includes'}{$included} = 1;
} else {
if ($after !~ /bogus/i) {
$silent || warn "Can't find file: $included\n";
}
}
}
}
close FILE;
}
sub print_includes {
my $self = shift;
my $target = shift;
my $len_sum = shift;
my $file;
my $len;
foreach $file (keys %{$self->{'includes'}}) {
if (!$flist{$file}) {
$flist{$file} = 1;
my $ref = $inc_files{$file};
if ($longpath) {
$len = length($ref->{'filepath'}) + 1;
} else {
$len = length($ref->{'Source_File'}) + 1;
}
if (($len_sum + $len > $ll) &&
(length($target) + 1 < $len_sum)) {
print "\n$target:";
$len_sum = length($target) + 1;
}
if ($longpath) {
print " " . $ref->{'filepath'};
} else {
print " " . $ref->{'Source_File'};
}
$len_sum += $len;
$len_sum = $ref->print_includes($target, $len_sum);
}
}
$len_sum;
}
# return list of modules used by included files
sub inc_mods {
my $self = shift;
my($file, $ref, $mod, @sub_list);
my @list = ();
foreach $mod (keys %{$self->{'uses'}}) {
push(@list, $mod);
}
foreach $file (keys %{$self->{'includes'}}) {
$ref = $inc_files{$file};
@sub_list = $ref->inc_mods();
@list = (@list, @sub_list);
}
@list;
}
# filenames containing the modules used by file and all its includes
sub find_mods {
my $self = shift;
my($modname, $file);
my @module_files = ();
my @mod_list = ();
# find modules used by include files
if (%{$self->{'includes'}}) {
foreach $file (keys %{$self->{'includes'}}) {
my $ref = $inc_files{$file};
my @list = $ref->inc_mods();
@mod_list = (@mod_list, @list);
}
}
# add them to the uses list (hash ensures uniqueness)
foreach $modname (@mod_list) {
$self->{'uses'}{$modname} = 1;
}
# now find the filename that contains the module information
foreach $modname (keys %{$self->{'uses'}}) {
if ($main::depend eq "obj") {
if ($file = $main::mod_files{$modname}) {
my $base = main::basename($file, @main::suffixes);
$file = $base . "." . $main::obj_ext;
push(@module_files, $file);
} elsif ( !$silent ) {
warn "Don't know where module $modname lives.\n";
}
} else {
if ($main::libdeps or defined($main::mod_files{$modname})) {
$modname .= "." . $main::mod_ext;
push(@module_files, $modname);
} elsif ( !$silent ) {
warn "Couldn't locate source for module $modname\n";
}
}
}
sort(@module_files);
}
sub print {
my $self = shift;
my $source = $self->{'Source_File'};
my $compile_string = "\t" . '$(F90) $(F90FLAGS) -c';
my($len_sum, $len);
my($base, $object, $modname, $flag, $target, $ftarget);
$base = main::basename($source, @main::suffixes);
$base = $main::obj_dir . '/' . $base if $main::obj_dir;
$target = $base . "." . $main::obj_ext;
if ($main::cpp) {
$ftarget = $base . "." . $main::ext;
}
$flag = $main::obj_dep_flag;
# print out "include" dependencies
%flist = ();
if (%{$self->{'includes'}}) {
$len_sum = length($target) + 1;
if ($main::add_ext) {
print "$base.$main::add_ext ";
$len_sum += length($base) + length($main::add_ext) + 2;
}
print "$target:";
$self->print_includes($target, $len_sum);
print "\n";
if ($main::cpp) {
%flist = ();
$len_sum = length($ftarget) + 1;
print "$ftarget:";
$self->print_includes($ftarget, $len_sum);
print "\n";
}
}
# clean out "use" of modules in own file
my $mod;
foreach $mod ( keys %{$self->{'uses'}} ) {
if ( ${$self->{'modules'}}{$mod} ) {
delete ${$self->{'uses'}}{$mod};
}
}
# print out "use" dependencies
if (%{$self->{'uses'}} || %{$self->{'includes'}}) {
my @module_files = $self->find_mods();
$len_sum = 0;
my $file;
foreach $file (@module_files) {
$file = $main::mod_dir . '/' . $file if $main::mod_dir;
if( $len_sum < 1 ) {
$len_sum = length($target) + 1;
print "$target:";
}
$len = length($file) + 1;
if (($len_sum + $len > $ll) &&
(length($target) + 1 < $len_sum)) {
print "\n$target:";
$len_sum = length($target) + 1;
}
$len_sum += $len;
print " " . $file;
}
if ($main::need_f) {
$len = length($ftarget) + 1;
if (($len_sum + $len > $ll) &&
(length($target) + 1 < $len_sum)) {
print "\n$target:";
$len_sum = length($target) + 1;
}
print " " . $ftarget if $len_sum;
}
print "\n" if $len_sum;
# extra Cray / Parasoft stuff
if ($flag) {
print $compile_string;
foreach $file (@module_files) {
print $flag . $file;
}
if ($main::cpp) {
print " " . $ftarget . "\n";
} else {
print " " . $source . "\n";
}
}
}
}
__END__
sfmakedepend - Fortran Dependency Checker
=head1 SYNOPSIS
sfmakedepend [--help] [--man] [--file=file] [-I dir]
[--srcdir dir] [--objdir dir] [--moddir dir]
[--fext ext] [--objext ext] [--modext ext]
[-D TAG=file] [--addext ext] [--case=up|down|asis]
[--compiler=crayold|cray|sgiold|sgi|nag|ibm|
parasoft|hp|absoft|sun]
[--depend=mod|obj] [--cpp] [--libdeps] [--drop]
[--silent] file ...
=head1 DESCRIPTION
This is a makedepend script for Fortran, including Fortran 90.
It searches for Fortran style includes, C preprocessor includes,
and module dependencies to the extent that I understand them.
Your files must have an extension listed in the @suffixes list
in the code. You might also want to modify $compile_string;
the compiler is called $(F90).
=head1 OPTIONS AND ARGUMENTS
=over 4
=item I<--help>
Print more details about the arguments.
=item I<--man>
Print a full man page.
=item I<--file file>
Change the name of the current Makefile (default is Makefile).
Use "--file -" to write to stdout.
=item I<-I dir>
Look in alternate directories for the include files. There can be
several "-I dir" options used at once. The current directory is
still searched first.
=item I<--srcdir dir>
Look in alternate directories for the source files, much like VPATH.
It can be used multiple times to search in more than one directory.
=item I<--objdir dir>
Tells sfmakedepend to prepend objdir to all object references (and
cpp output files if used). This is required if you use a build
directory that isn't your current directory.
=item I<--moddir dir>
Tells sfmakedepend to prepend moddir to all module references. This
is required if you use a common module library directory for a
multi-directory project (+moddir= .. option on HP, eg.).
=item I<--fext>
This is used with the --cpp switch for compilers
which expect an extension other than .f on source files. For
instance, one might choose to use "--fext f90".
=item I<--objext ext>
Tells sfmakedepend what extension to use for object files. The
default is "o", but "obj", for instance, is
appropriate on MS-DOG etc.
=item I<--modext ext>
Specifies the extension to use for Fortran 90 module files. The default
extension is "mod" since this seems to be an emerging standard. Let
me know if other compilers use a different filename for the module
information (keep that --compiler option up to date).
=item I<--D tag=file>
Tells sfmakedepend to search the source files for lines such as:
#include MY_CHEESE
Where MY_CHEESE is set with a -DMY_CHEESE="wensleydale.h".
=item I<--addext ext>
Tells sfmakedepend to add targets with extension add_ext to the rules
for object files. For instance, to operate with (f77) ftnchek .prj
files, you could use
`--addext prj' to get rules like:
foo.prj foo.o: ...
=item I<--case up|down|asis>
Controls case of module names when generating module file names. Only
relevant where the module file name is named after the module rather
than after the source file.
=item I<--compiler=crayold|cray|sgiold|sgi|nag|ibm|parasoft|hp|absoft|sun>
Controls the type of target compiler, setting the module name and other
options appropriately. The cray option assumes that FFLAGS includes
-e m for creating the .mod file, while crayold refers to the default of
including that information in the object file.
=item I<--depend=mod|obj>
Whether to use the module information file or the module object file
in dependencies.
=item I<--cpp>
There are times when one might choose to run .F files through cpp and
to keep the .f files (for the debugger or for a custom preprocessor).
In that case, make must be told about the cpp include dependencies of
the .f files. This option will provide those dependencies.
=item I<--libdeps>
Generate dependencies on modules for which source code is not
available. Otherwise a warning is issued, but the dependency is not
listed.
=item I<--longpath>
Keep the full path to the include files in the dependency. Otherwise,
just print the filename and use VPATH to provide the directory
information.
=item I<--drop>
Drop module dependencies (my_mod.mod: my_mod.o). This is also done when
--depend=obj.
=item I<--silent>
The default is to warn about unfound files. This option causes those
warnings to not be printed.
=item I<[file ...]>
The list of source files to search for dependencies.
=back
=head1 EXAMPLE
Search for include files in /usr/local/include:
sfmakedepend --cpp --fext=f90 -I /usr/local/include *.F
Example usage in gnuMakefile:
SRCDIRS= srcdir1 srcdir2 srcdir3 ...
FSRCS0 := $(foreach DIR, . $(SRCDIRSH),$(wildcard $(DIR)/*.f))
FSRCS := $(sort $(notdir $(FSRCS0)))
F_makedepend=sfmakedepend --file - $(addprefix --srcdir ,$(SRCDIRSH)) \
$(subst -I,-I ,$(Includes))
depend $(MAKEFILE_INC):
$(F_makedepend) $(FSRCS) >> $(MAKEFILE_INC)
include $(MAKEFILE_INC)
=head1 AUTHOR
Kate Hedstrom (kate@arsc.edu)
First Perl 5 Fortran 90 version November 1994.
=head1 CONTRIBUTORS
Dave Love (d.love@dl.ac.uk)
Added the --objext and --addext options (1996).
Patrick Jessee
Added hp support (1997, now in --compiler option).
Sergio Gelato (gelato@tac.dk)
Added the --compiler, --depend, --case options, and
--(no)libdeps (1998).
Tobias Buchal (buchal@ifh.bau-verm.uni-karlsruhe.de)
Added the --srcdir and --file - options (1999).
Klaus Ramstock (klaus@tdm234.el.utwente.nl)
Added the --moddir option (1999).
Sandra Schroedter (sandra@fsg-ship.de)
Fix to preserve Makefile links (1999).
Holger Bauer (bauer@itsm.uni-stuttgart.de)
Added the --drop option (2000).
Gavin Salam (salam@lpthe.jussieu.fr)
Made it recognize multiple "use"s on one line (2005).
Others I've doubtless forgotten.
=cut
#
# NOTES
# This makedepend script is my first attempt at using perl 5
# objects. Therefore, it may not be the best example of how
# to do this. Also, it requires perl 5 and will die if you
# to use it with an older perl. The latest version is
# available from:
#
# http://www.arsc.edu/~kate/Perl/
# ftp://ahab.rutgers.edu/pub/perl/sfmakedepend
#
# Fortran 90 introduces some interesting dependencies. Two
# compilers I have access to (NAG f90 and IBM xlf) produce a
# private "mod_name.mod" file if you define "module mod_name"
# in your code. This file is used by the compiler when you
# use the module as a consistency check (type-safe). On the
# other hand, the Cray and Parasoft compilers store the module
# information in the object file and then files which use the
# modules need to be compiled with extra flags pointing to the
# module object files.
#
# This script assumes that all the files using and defining
# modules are in the same directory and are all in the list of
# files to be searched. It seems that the industry has not
# settled on a practical way to deal with a separate modules
# directory, anyway.
#
# I sometimes include non-existent files as a compile time
# consistency check:
#
# #ifndef PLOTS
# #include "must_define_PLOTS" /* bogus include */
# #endif
#
# This program warns about include files it can't find, but
# not if there is a "bogus" on the same line.
#
# * The f90 module dependencies can confuse some versions of
# make, especially of the System V variety. We use gnu
# make because it has no problems with these dependencies.
#
# BUGS
# It can sometimes produce duplicate dependencies.
#
# It treats C preprocessor includes the same as Fortran
# includes. This can add unnecessary dependencies if you
# use the -s flag and both kinds of includes.
#
# Please let me know if you find any others.
# Kate Hedstrom
# kate@arsc.edu