#! /usr/bin/env perl # -*- Mode: perl; -*- # # f77tof90 indir outdir [ Makefile-template [Make-Append] ] # For each file in indir/*.[fF], create a corresponding file in outdir # with .f90/.F90, and with any include "mpif.h" replaced with use mpi # It also changes to the new comment style, because some compilers # use the comment style to choose other features of the language # # We also allow the file name to be modified to help out Windows, since # programs in a project need to have distinct names # use warnings; $indir = $ARGV[0]; $outdir = $ARGV[1]; $makeTemplate = $ARGV[2]; $makeAppend = $ARGV[3]; $convertToFreeForm = 1; $convertToNewComments = 1; # Including a newline variable allows us to handle Unix and DOS source files $newline = "\n"; %replaceInclude = ( 'iodisp' => 'integer (kind=MPI_OFFSET_KIND) disp', 'ioaint' => 'integer (kind=MPI_ADDRESS_KIND) aint', 'iooffset' => 'integer (kind=MPI_OFFSET_KIND) offset', 'type1aint' => 'integer (kind=MPI_ADDRESS_KIND) aint', 'typeaints' => 'integer (kind=MPI_ADDRESS_KIND) aint, aintv(max_asizev)', 'attr1aints' => 'integer (kind=MPI_ADDRESS_KIND) extrastate, valin, valout, val', 'attraints' => 'integer (kind=MPI_ADDRESS_KIND) extrastate, valin, valout, val', 'addsize' => 'integer (kind=MPI_ADDRESS_KIND) asize', 'add1size' => 'integer (kind=MPI_ADDRESS_KIND) asize', ); %excludePrograms = (); $debugReplace = 0; $reportSkipped = 1; # -------------------------------------------------------------------------- # Check the input arguments if ($indir eq "" || $outdir eq "") { print STDERR "Usage: f77tof90 indir outdir [ makefile-template ]\n"; exit 1; } if ( ! -d $indir) { print STDERR "Input directory $indir does not exist\n"; exit 1; } if (! -d $outdir) { print STDERR "Output directory $outdir does not exist\n"; exit 1; } # -------------------------------------------------------------------------- # -------------------------------------------------------------------------- opendir( DIR, "$indir" ); my @filelist = (); while ($file = readdir(DIR)) { # Extract the extension if ($file =~ /^(.*)\.([^\.]*)$/) { $name = $1; $ext = $2; # Special handling for C files, if any if ($ext eq "c") { my $name90 = $name; # don't transform "ctypesfromc" to "ctypesf90romc" if ($name ne "ctypesfromc") { $name90 =~ s/f/f90/g; } &ConvertCFile( "$indir/$file", "$outdir/$name90.c.new" ); &ReplaceIfDifferent( "$outdir/$name90.c", "$outdir/$name90.c.new" ); next; } # Skip if the file isn't a Fortran source file if ($ext ne "f" && $ext ne "F") { next; } &ConvertToF90( "$indir/$file", "$outdir/${name}90.${ext}90.new" ); &ReplaceIfDifferent( "$outdir/${name}90.${ext}90", "$outdir/${name}90.${ext}90.new" ); $filelist[$#filelist+1] = $file; } } closedir( DIR ); # &CreateMakefile( "filelist", $outdir ); if (defined($makeTemplate) && $makeTemplate ne "" && -s "$indir/$makeTemplate") { &ConvertMakefile( $indir, $outdir, $makeTemplate ); if (defined($makeAppend) && -s "$outdir/$makeAppend") { # If there is a makeAppend in the output directory, then # append that to the generated makefile &AppendFile( "$outdir/$makeAppend", "$outdir/$makeTemplate.new" ); } &ReplaceIfDifferent( "$outdir/$makeTemplate", "$outdir/$makeTemplate.new" ); # unconditionally update the stamp file to assist the maintainer rebuild # rules open(STAMP, '>', "$outdir/Makefile.am-stamp"); print STAMP localtime()."\n"; close(STAMP); } if (-s "$indir/testlist" || -s "$indir/testlist.in") { # We allow both testlist.in and testlist as source files; # testlist.in gets priority my $filename = "testlist"; if (-s "$indir/testlist.in") { $filename = "testlist.in"; } &ConvertTestlist( $indir, $outdir, $filename ); if (-s "$outdir/testlist.ap") { &AppendFile( "$outdir/testlist.ap", "$outdir/$filename.new" ); } &ReplaceIfDifferent( "$outdir/$filename", "$outdir/$filename.new" ); } exit 0; # ----------------------------------------------------------------------------- sub ConvertToF90 { my $infile = $_[0]; my $outfile = $_[1]; open (INF, "<$infile" ) || die "Could not open $infile\n"; open (OUTF, ">$outfile" ) || die "Could not open $outfile\n"; print OUTF "! This file created from $infile with f77tof90\n"; my $lastLine = ""; my $firstline = 1; while () { if (/\r/) { $newline = "\r\n"; } # Remove any end-of-line characters s/[\r\n]*//g; # The implicit none must not come before the use mpi statement, # but in F77, it must come before the include mpif.h statement. # Rather than try and fix this, rely on the F77 versions to # catch undeclared variables if (/[Ii][Mm][Pp][Ll][Ii][Cc][Ii][Tt]\s+[Nn][Oo][Nn][Ee]/) { next; } if (/^(\s*)include\s+[\'\"]mpif\.h/) { $_ = "$1use mpi"; } # Allow the insertion of Fortran 90 only statements, such as # interface definitions if (/^CF90/) { s/^CF90/ /; } # Remove lines used only for F77, such as external MPI function declarations if (/!\s*F77ONLY\s*$/i) { $_ = ""; } # Since we use interface statements for the error handlers, # remove their external declaration if (/^\s+external myerrhanfunc/) { s/^\s/!/; } if ($convertToNewComments) { s/^C/!/; s/^c/!/; } # Update the special includes that are used to provide # address or offset sized types with ones the use the # Fortran90 KIND style if (/^(\s*)include\s+[\'\"]([\/\.\w]+)\.h[\"\']/) { my $leading = $1; my $includename = $2; if (defined($replaceInclude{$includename})) { $_ = $leading . $replaceInclude{$includename} . "\n"; } } # We need to handle the special case of the program # name in spawn commands if (/(.*)\"([\.\/\w]*spawn[^\"]*)\"(.*)/) { my $before = $1; my $name = $2; my $after = $3; $_ = $before . "\"" . $name . "90" . "\"" . $after; } # We could also detect continuations in column six and # convert to free-form input by holding one line back. if ($convertToFreeForm) { if (/^ \S(.*)/) { $leftover = $1; # This line contains a continuation marker # Add a continuation marker to the previous line if # it doesn't already have one if (! ($lastline =~ /\&\s*$/) ) { $lastline .= " &"; } $_ = " \&$leftover"; } } print OUTF "$lastline$newline" if (! $firstline); $firstline = 0; $lastline = $_; } print OUTF "$lastline$newline"; close (INF); close (OUTF); } # # A very simple routine for creating a version of a C file that refers # to F90 instead of F77. sub ConvertCFile { my $infile = $_[0]; my $outfile = $_[1]; open (INF, "<$infile" ) || die "Could not open $infile\n"; open (OUTF, ">$outfile" ) || die "Could not open $outfile\n"; print OUTF "/* This file created from $infile with f77tof90 */\n"; while () { if (/\r/) { $newline = "\r\n"; } # Remove any end-of-line characters s/[\r\n]*//g; # replace F77 with F90, mostly for CPP tests, except for name # mapping if (! /F77_NAME/) { s/F77/F90/g; } print OUTF "$_$newline"; } close (INF); close (OUTF); } # Create a makefile from a template. Replace @EXECS@ with the programs # in the filelist. # CreateMakefile( "filelist", $outdir ) sub CreateMakefile { my $filelist = $_[0]; my $outdir = $_[1]; print STDERR "This function is not implemented\n"; return 0; } # # Take an existing makefile and perform the following transformations: # .f -> .f90, .F -> .F90 # Others as necessary # ConvertMakefile( indir, outdir, filename ) # By providing the filename, we can accept Makefile, Makefile.in, Makefile.ap, # Makefile.sm, or even nonstandard names such as buildscript. sub ConvertMakefile { my ($indir, $outdir, $filename) = @_; %excludePrograms = (); my $saw_maintainercleanfiles = 0; my $saw_extra_dist = 0; open( INF, "<$indir/$filename" ) || die "Cannot open $indir/$filename\n"; open( OUTF, ">$outdir/$filename.new" ) || die "Cannot open $outdir/$filename.new\n"; print OUTF "# This $filename generated automatically by f77tof90\n"; print OUTF "# from $indir/$filename. DO NOT EDIT\n"; while () { if (not m/^\s*#/ and m/MAINTAINERCLEANFILES/) { $saw_maintainercleanfiles = 1; } if (not m/^\s*#/ and m/EXTRA_DIST/) { $saw_extra_dist = 1; } # First, check for sources that are not present. These # may be derived files (see f77/io for an example). For now, # we'll skip these unless they are explicitly expected to not be present # (by the presence of a "nodist_" prefix. if (/^(nodist_)?(\w+)_SOURCES\s*=\s*(\w+\.f)/) { my $nodist = $1; my $sourcebase = $2; my $sourcename = $3; if (not $nodist and ! -s "$indir/$sourcename") { print "Skipping source file $indir/$sourcename because it is not present\n" if $reportSkipped; $excludePrograms{$sourcebase} = 1; next; } } # Headers like f77/spawn/type1aints.h are not used in the f90 tests. # They are instead replaced with appropriate "kind=" type declarations # in a different part of this script. So we drop any lines that add # dependencies on a header. if (m/^\w+f\.\$\(OBJEXT\):.*\.h/) { my $chomped = $_; chomp $_; next; } # convert program names from foof.f to foof90.f90 s/f_SOURCES/f90_SOURCES/g; s/f_DEPENDENCIES/f90_DEPENDENCIES/g; if (/f\.f/) { s/f\.f/f90.f90/g; } else { # Move files to f90 s/\.f/.f90/g; } s/mtestf\.o/mtestf90.o/; s/\.F/.F90/g; s/f77/f90/g; s/F77/FC/g; # Update any per-program LDADD values s/f_LDADD/f90_LDADD/g; # # Handle special cases: # Force the c2f2c test to use the f90 compiler s/c2f2cf90_SOURCES.*/c2f2cf90_SOURCES = c2f2cf90.f90 c2f902c.c/; s/c2f2ciof90_SOURCES.*/c2f2ciof90_SOURCES = c2f2ciof90.f90 c2f902cio.c/; # s/c2f2ciof90_LDADD/c2f2cfio90_LDADD/g; s/c2f2cwinf90_SOURCES.*/c2f2cwinf90_SOURCES = c2f2cwinf90.f90 c2f902cwin.c/; s/c2fmult/c2f90mult/g; if (/EXTRA_PROGRAMS/) { s/allocmemf/allocmemf90/; # allocmemf test is special } # Handle the special case of C programs (used for f2c/c2f testing) if (/^(\w+)_SOURCES(\s*=\s*)(\w+)\.c\s*$/) { my $progname = $1; my $spacing = $2; my $name = $3; if ($name !~ m/ctypesfromc/) { $name =~ s/f(?!90)/f90/; $progname =~ s/f(?!90)/f90/; $_ = "$progname" . "_SOURCES" . $spacing . $name . ".c\n"; } } # Catch any bare "Xf" tests listed in noinst_PROGRAMS or # EXTRA_PROGRAMS. Check for "Xf.f" to avoid substituting any random # word that ends in "f". We also cheat a bit and look into the # ioharness.defn in the indir directory for valid file names. if (not m/^\s*#/) { while (m/\b(\w+f)\b/g) { my $word = $1; next if $word eq "if" or $word eq "rf" or $word eq "endif"; # filter out some noise if (-e "$indir/${word}.f" or 0 == system(qq(grep 'TESTDEFN filename="${word}\\.f"' '$indir/ioharness.defn' >/dev/null 2>&1))) { s/\b${word}\b/${word}90/g; } else { print "Skipping word '${word}' because file $indir/${word}.f is not present\n" if $reportSkipped; } } } # f90 makefiles should use the corresponding common automake fragment s/Makefile_f77.mtest/Makefile_f90.mtest/g; # Eventually need some way to update directory paths (particularly # relative ones) and add F90 compile rules when not present. print OUTF $_; } my $mcf_plus = ''; if ($saw_maintainercleanfiles) { $mcf_plus = '+'; } my $ed_plus = ''; if ($saw_extra_dist) { $ed_plus = '+'; } my $testlist_ap = ''; if (-f "${outdir}/testlist.ap") { $testlist_ap = 'testlist.ap'; } my $makefile_ap = ''; if (-f "${outdir}/Makefile.ap") { $makefile_ap = 'Makefile.ap'; } # append a rule to help remake the f90 Makefile.am if the f77 Makefile.am is # updated print OUTF <>$outfile" ) || die "Cannot open $outfile\n"; while () { print OUTA $_; } close(INA); close(OUTA); } # # Replace old file with new file only if new file is different # Otherwise, remove new filename sub ReplaceIfDifferent { my ($oldfilename,$newfilename) = @_; my $rc = 1; if (-s $oldfilename) { $rc = system "cmp -s $newfilename $oldfilename"; $rc >>= 8; # Shift right to get exit status } if ($rc != 0) { print STDERR "Replacing $oldfilename\n"; if ($debugReplace && -s $oldfilename) { print STDERR "Differences are:"; system "diff $newfilename $oldfilename"; } # The files differ. Replace the old file # with the new one if (-s $oldfilename) { unlink $oldfilename; } rename $newfilename, $oldfilename || die "Could not replace $oldfilename"; } else { unlink $newfilename; } } # Change the names of the tests. Remove any that were skipped from the # Makefile. Check for a testlist.in before testlist sub ConvertTestlist { my ($indir, $outdir, $filename) = @_; open( INF, "<$indir/$filename" ) || die "Cannot open $indir/$filename\n"; open( OUTF, ">$outdir/$filename.new" ) || die "Cannot open $outdir/$filename.new\n"; print OUTF "# This file generated by f77tof90\n"; while () { if (/^(\w+)\s/) { my $sourcebase = $1; if (defined($excludePrograms{$sourcebase})) { next; } } if (/^(\w+f)\s+(.*)/) { $_ = $1 . "90 " . $2 . "\n"; } elsif (/^c2fmult(\w*)\s+(.*)/) { # This is a special case for programs that are not Fortran # programs but are part of the Fortran tests; principly, these # are the tests of MPI handle conversion # note the \w* instead of \w+; this allows us to match both # c2fmult.c and c2fmultio.c $_ = "c2f90mult$1 $2\n"; } elsif (/^\@ALLOCMEMF\@/) { # This is a special case for an optional feature (using # Cray-style pointers for MPI_Alloc_mem). $_ = "\@ALLOCMEMFC\@\n"; } elsif (/^(\@\w+\@)(\s*\w+f)\s+(.*)/) { # This handles the case where an autoconf variable in the # testlist.in file is used to optionally comment out a test $_ = $1 . $2 . "90 " . $3 . "\n"; } print OUTF $_; } close INF; close OUTF; }