#!/usr/bin/env perl # Generate dependencies in a form suitable for inclusion into a Makefile. # The source filenames are provided in a file, one per line. Directories # to be searched for the source files and for their dependencies are provided # in another file, one per line. Output is written to STDOUT. # # For CPP type dependencies (lines beginning with #include), or for Fortran # include dependencies, the dependency search is recursive. Only # dependencies that are found in the specified directories are included. # So, for example, the standard include file stdio.h would not be included # as a dependency unless /usr/include were one of the specified directories # to be searched. # # For Fortran module USE dependencies (lines beginning with a case # insensitive "USE", possibly preceded by whitespace) the Fortran compiler # must be able to access the .mod file associated with the .o file that # contains the module. In order to correctly generate these dependencies # the following restriction must be observed. # # ** All modules that are to be contained in the dependency list must be # ** contained in one of the source files in the list provided on the command # ** line. # # The reason for this restriction is that the modules have a nominal dependence # on the .o files. If a module is being used for which the source code is not # available (e.g., a module from a library), then adding a .o dependency for # that module is a mistake because make will attempt to build that .o file, and # will fail if the source code is not available. # # Original version: B. Eaton # Climate Modelling Section, NCAR # Feb 2001 # # ChangeLog: # ----------------------------------------------------------------------------- # Modifications to Brian Eaton's original to relax the restrictions on # source file name matching module name and only one module per source # file. Also added a new "-d depfile" option which allows an additional # file to be added to every dependence. # # # Tom Henderson # Global Systems Division, NOAA/OAR # Mar 2011 # ----------------------------------------------------------------------------- # Several updates: # # - Remove limitation that modules cannot be named "procedure". # # - Allow optional "::" in use statement (Fortran 2003). # # - Instead of having .o files depend on other .o files directly, # have them depend indirectly through the .mod files. This allows # the compiler to have discretion over whether to update a .mod, # and prevents cascading recompilation when it does not. # # # Sean Santos # CESM Software Engineering Group, NCAR # Mar 2013 # ----------------------------------------------------------------------------- # More updates: # # - Restore ability to recognize .mod files in the path, if there's no source # file that provides the same module. # # - Allow "non_intrinsic" keyword (Fortran 2003). # # Sean Santos # CESM Software Engineering Group, NCAR # Mar 2013 # ----------------------------------------------------------------------------- use Getopt::Std; use File::Basename; # Check for usage request. @ARGV >= 2 or usage(); # Process command line. my %opt = (); getopts( "t:wd:m:", \%opt ) or usage(); my $filepath_arg = shift() or usage(); my $srcfile_arg = shift() or usage(); @ARGV == 0 or usage(); # Check that all args were processed. my $obj_dir = ""; if ( defined $opt{'t'} ) { $obj_dir = $opt{'t'}."/"; } my $additional_file = ""; if ( defined $opt{'d'} ) { $additional_file = $opt{'d'}; } my $mangle_scheme = "lower"; if ( defined $opt{'m'} ) { $mangle_scheme = $opt{'m'}; } open(FILEPATH, $filepath_arg) or die "Can't open $filepath_arg: $!\n"; open(SRCFILES, $srcfile_arg) or die "Can't open $srcfile_arg: $!\n"; # Make list of paths to use when looking for files. # Prepend "." so search starts in current directory. This default is for # consistency with the way GNU Make searches for dependencies. my @file_paths = ; close(FILEPATH); chomp @file_paths; unshift(@file_paths,'.'); foreach $dir (@file_paths) { # (could check that directories exist here) $dir =~ s!/?\s*$!!; # remove / and any whitespace at end of directory name ($dir) = glob $dir; # Expand tildes in path names. } # Make list of files containing source code. my @src = ; close(SRCFILES); chomp @src; my %module_files = (); # Attempt to parse each file for /^\s*module/ and extract module names # for each file. my ($f, $name, $path, $suffix, $mod); my @suffixes = ('\.[fF]90', '\.[fF]','\.F90\.in' ); foreach $f (@src) { ($name, $path, $suffix) = fileparse($f, @suffixes); # find the file in the list of directorys (in @file_paths) my $file_path = find_file($f); open(FH, $file_path) or die "Can't open $file_path: $!\n"; while ( ) { # Search for module definitions. if ( /^\s*MODULE\s+(\w+)\s*(\!.*)?$/i ) { ($mod = $1) =~ tr/A-Z/a-z/; if ( defined $module_files{$mod} ) { die "Duplicate definitions of module $mod in $module_files{$mod} and $name: $!\n"; } $module_files{$mod} = $name; } } close( FH ); } # Now make a list of .mod files in the file_paths. If a source dependency # can't be found based on the module_files list above, then maybe a .mod # module dependency can if the mod file is visible. my %trumod_files = (); my ($dir); my ($f, $name, $path, $suffix, $mod); # This might not be clear: we want to mangle a "\" so that it will escape # the "." in .mod or .MOD my @suffixes = (mangle_modfile("\\")); foreach $dir (@file_paths) { # Similarly, this gets us $dir/*.mod or $dir/*.MOD @filenames = (glob("$dir/".mangle_modfile("*"))); foreach $f (@filenames) { ($name, $path, $suffix) = fileparse($f, @suffixes); ($mod = $name) =~ tr/A-Z/a-z/; $trumod_files{$mod} = $name; } } #print STDERR "\%module_files\n"; #while ( ($k,$v) = each %module_files ) { # print STDERR "$k => $v\n"; #} # Find module and include dependencies of the source files. my ($file_path, $rmods, $rincs); my %file_modules = (); my %file_includes = (); my @check_includes = (); my %modules_used = (); foreach $f ( @src ) { # Find the file in the seach path (@file_paths). unless ($file_path = find_file($f)) { if (defined $opt{'w'}) {print STDERR "$f not found\n";} next; } # Find the module and include dependencies. ($rmods, $rincs) = find_dependencies( $file_path ); # Remove redundancies (a file can contain multiple procedures that have # the same dependencies). $file_modules{$f} = rm_duplicates($rmods); $file_includes{$f} = rm_duplicates($rincs); # Make a list of all include files. push @check_includes, @{$file_includes{$f}}; } #print STDERR "\%file_modules\n"; #while ( ($k,$v) = each %file_modules ) { # print STDERR "$k => @$v\n"; #} #print STDERR "\%file_includes\n"; #while ( ($k,$v) = each %file_includes ) { # print STDERR "$k => @$v\n"; #} #print STDERR "\@check_includes\n"; #print STDERR "@check_includes\n"; # Find include file dependencies. my %include_depends = (); while (@check_includes) { $f = shift @check_includes; if (defined($include_depends{$f})) { next; } # Mark files not in path so they can be removed from the dependency list. unless ($file_path = find_file($f)) { $include_depends{$f} = -1; next; } # Find include file dependencies. ($rmods, $include_depends{$f}) = find_dependencies($file_path); # Add included include files to the back of the check_includes list so # that their dependencies can be found. push @check_includes, @{$include_depends{$f}}; # Add included modules to the include_depends list. if ( @$rmods ) { push @{$include_depends{$f}}, @$rmods; } } #print STDERR "\%include_depends\n"; #while ( ($k,$v) = each %include_depends ) { # print STDERR (ref $v ? "$k => @$v\n" : "$k => $v\n"); #} # Remove include file dependencies that are not in the Filepath. my $i, $ii; foreach $f (keys %include_depends) { unless (ref $include_depends{$f}) { next; } $rincs = $include_depends{$f}; unless (@$rincs) { next; } $ii = 0; $num_incs = @$rincs; for ($i = 0; $i < $num_incs; ++$i) { if ($include_depends{$$rincs[$ii]} == -1) { splice @$rincs, $ii, 1; next; } ++$ii; } } # Substitute the include file dependencies into the %file_includes lists. foreach $f (keys %file_includes) { my @expand_incs = (); # Initialize the expanded %file_includes list. my $i; unless (@{$file_includes{$f}}) { next; } foreach $i (@{$file_includes{$f}}) { push @expand_incs, $i unless ($include_depends{$i} == -1); } unless (@expand_incs) { $file_includes{$f} = []; next; } # Expand for ($i = 0; $i <= $#expand_incs; ++$i) { push @expand_incs, @{ $include_depends{$expand_incs[$i]} }; } $file_includes{$f} = rm_duplicates(\@expand_incs); } #print STDERR "expanded \%file_includes\n"; #while ( ($k,$v) = each %file_includes ) { # print STDERR "$k => @$v\n"; #} # Print dependencies to STDOUT. print "# Declare all module files used to build each object.\n"; foreach $f (sort keys %file_modules) { my $file; if($f =~ /\.F90\.in$/){ $f =~ /(.+)\.F90\.in/; $file = $1; }else{ $f =~ /(.+)\./; $file = $1; } $target = $obj_dir."$file.o"; print "$target : @{$file_modules{$f}} @{$file_includes{$f}} $additional_file \n"; } print "# The following section relates each module to the corresponding file.\n"; $target = mangle_modfile("%"); print "$target : \n"; print "\t\@\:\n"; foreach $mod (sort keys %modules_used) { my $mod_fname = $obj_dir.mangle_modfile($mod); my $obj_fname = $obj_dir.$module_files{$mod}.".o"; print "$mod_fname : $obj_fname\n"; } #-------------------------------------------------------------------------------------- sub find_dependencies { # Find dependencies of input file. # Use'd Fortran 90 modules are returned in \@mods. # Files that are "#include"d by the cpp preprocessor are returned in \@incs. # Check for circular dependencies in \@mods. This type of dependency # is a consequence of having multiple modules defined in the same file, # and having one of those modules depend on the other. my( $file ) = @_; my( @mods, @incs ); open(FH, $file) or die "Can't open $file: $!\n"; # Construct the makefile target associated with this file. This is used to # check for circular dependencies. my ($name, $path, $suffix, $target); my @suffixes = ('\.[fF]90', '\.[fF]','\.F90\.in' ); ($name, $path, $suffix) = fileparse($file, @suffixes); $target = "$name.o"; while ( ) { # Search for "#include" and strip filename when found. if ( /^#include\s+[<"](.*)[>"]/ ) { push @incs, $1; } # Search for Fortran include dependencies. elsif ( /^\s*include\s+['"](.*)['"]/ ) { #" for emacs fontlock push @incs, $1; } # Search for module dependencies. elsif ( /^\s*USE(?:\s+|\s*\:\:\s*|\s*,\s*non_intrinsic\s*\:\:\s*)(\w+)/i ) { # Return dependency in the form of a .mod file ($module = $1) =~ tr/A-Z/a-z/; if ( defined $module_files{$module} ) { # Check for circular dependency unless ("$module_files{$module}.o" eq $target) { $modules_used{$module} = (); push @mods, "$obj_dir".mangle_modfile($module); } } # If we already have a .mod file around. elsif ( defined $trumod_files{$module} ) { push @mods, "$obj_dir".mangle_modfile($trumod_files{$module}); } } } close( FH ); return (\@mods, \@incs); } #-------------------------------------------------------------------------------------- sub find_file { # Search for the specified file in the list of directories in the global # array @file_paths. Return the first occurance found, or the null string if # the file is not found. my($file) = @_; my($dir, $fname); foreach $dir (@file_paths) { $fname = "$dir/$file"; if ( -f $fname ) { return $fname; } } return ''; # file not found } #-------------------------------------------------------------------------------------- sub rm_duplicates { # Return a list with duplicates removed. my ($in) = @_; # input arrary reference my @out = (); my $i; my %h = (); foreach $i (@$in) { $h{$i} = ''; } @out = keys %h; return \@out; } #-------------------------------------------------------------------------------------- sub mangle_modfile { # Return the name of the module file corresponding # to a given module. my ($mod) = @_; my $fname; if ($mangle_scheme eq "lower") { ($fname = $mod) =~ tr/A-Z/a-z/; $fname .= ".mod"; } elsif ($mangle_scheme eq "upper") { ($fname = $mod) =~ tr/a-z/A-Z/; $fname .= ".MOD"; } else { die "Unrecognized mangle_scheme!\n"; } return $fname; } #-------------------------------------------------------------------------------------- sub usage { ($ProgName = $0) =~ s!.*/!!; # name of program die <