#!/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 () { 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 () { $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