#!/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 since the makefile doesn't # contain rules to build .mod files the dependency takes the form of the .o # file that contains the module. 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 objfile" option which allows an additional # object file to be added to every dependence. # # One important limitation remains. If your module is named "procedure", # this script will quietly ignore it. # # Tom Henderson # Global Systems Division, NOAA/OAR # Mar 2011 # ----------------------------------------------------------------------------- use Getopt::Std; use File::Basename; # Check for usage request. @ARGV >= 2 or usage(); # Process command line. my %opt = (); getopts( "t:wd:", \%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_obj = ""; if ( defined $opt{'d'} ) { $additional_obj = $opt{'d'}; } 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]' ); 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+)/i ) { ($mod = $1) =~ tr/a-z/A-Z/; # skip "module procedure foo" statements if ( $mod ne "PROCEDURE" ) { if ( defined $module_files{$mod} ) { die "Duplicate definitions of module $mod in $module_files{$mod} and $name: $!\n"; } $module_files{$mod} = $name; } } } close( FH ); } #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 = (); 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. foreach $f (sort keys %file_modules) { $f =~ /(.+)\./; $target = "$1.o"; if ( defined $opt{'t'} ) { $target = "$opt{'t'}/$1.o"; } print "$target : $f @{$file_modules{$f}} @{$file_includes{$f}} $additional_obj \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]' ); ($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+(\w+)/i ) { # Return dependency in the form of a .o version of the file that contains # the module. ($module = $1) =~ tr/a-z/A-Z/; if ( defined $module_files{$module} ) { # Check for circular dependency unless ("$module_files{$module}.o" eq $target) { if ( defined $obj_dir ) { push @mods, "$obj_dir/$module_files{$module}.o"; } else { push @mods, "$module_files{$module}.o"; } } } } } 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 usage { ($ProgName = $0) =~ s!.*/!!; # name of program die <