diff --git a/ac/Makefile.in b/ac/Makefile.in index 2e482ab0c5..599381a35b 100644 --- a/ac/Makefile.in +++ b/ac/Makefile.in @@ -28,7 +28,7 @@ Makefile: @srcdir@/ac/Makefile.in config.status .PHONY: depend depend: Makefile.dep Makefile.dep: - $(MAKEDEP) -o Makefile.dep $(SRC_DIRS) + $(MAKEDEP) -o Makefile.dep -e $(SRC_DIRS) # Delete any files associated with configuration (including the Makefile). diff --git a/ac/deps/Makefile.fms.in b/ac/deps/Makefile.fms.in index fc580a8c9e..e2581cf817 100644 --- a/ac/deps/Makefile.fms.in +++ b/ac/deps/Makefile.fms.in @@ -22,4 +22,4 @@ ARFLAGS = @ARFLAGS@ .PHONY: depend depend: Makefile.dep Makefile.dep: - $(MAKEDEP) -o Makefile.dep -x libFMS.a @srcdir@ + $(MAKEDEP) -o Makefile.dep -e -x libFMS.a @srcdir@ diff --git a/ac/makedep b/ac/makedep index 74d9200ce8..443371a79f 100755 --- a/ac/makedep +++ b/ac/makedep @@ -1,225 +1,301 @@ -#!/bin/bash - -usage() { - echo "Construct Makfile.dep containing dependencies for F90 source code." - echo - echo "Syntax:" $0 "[-h|d] [-o FILE] [-x EXEC] PATH [PATH] [...]" - echo - echo "arguments:" - echo " PATH Directories containing source code. All subdirectories and" - echo " symbolic links are followed." - echo - echo "options:" - echo " -h Print this help message." - echo " -d Annotate the makefile with extra information." - echo " -o FILE Construct dependencies in FILE instead of Makefile.dep ." - echo " -x EXEC Name of executable to build. Fails if more than one" - echo " is found. If EXEC ends in .a then a library is built." - echo " -f CMD String to use in compile rule. Default is:" - echo " '$(FC) $(DEFS) $(FCFLAGS) $(CPPFLAGS) -c $<'" -} - -# Defaults -makefile=Makefile.dep -debug=0 -executable="" -librarymode=0 -compile_line='$(FC) $(DEFS) $(FCFLAGS) $(CPPFLAGS) -c $<' - -while getopts dho:x:f: option -do - case "${option}" - in - d)debug=1;; - h)usage; exit;; - o)makefile=${OPTARG};; - x)executable=${OPTARG};; - f)compile_line=${OPTARG};; - esac -done -SRC_DIRS=${@:$OPTIND} - -if [ -z "$SRC_DIRS" ]; then - echo "Error: no search path specified on command line!" - exit 1 -fi - -# Scan everything (Fortran related) (Fortran related) -A=$(find -L ${SRC_DIRS} \( -name "*.F90" -o -name "*.f90" \) ) # all source files -I=`find -L ${SRC_DIRS} \( -name "*.h" -o -name "*.inc" \) | xargs dirname | sort | uniq | sed 's:^:-I:'` # include paths to pass to cpp when checking to see which .h files are used -O=() -externals=() -declare -A o2src o2mod o2use o2H o2head o2inc p2o o2p all_modules -for F in ${A}; do # F is the relative path to source file - f=`basename $F` # file name stripped of path - o=${f/.?90/}.o # object file name - o2src["$o"]=$F - m=`egrep -i "^ *module " $F | sed 's/ *!.*//' | grep -vi procedure | tr '[A-Z]' '[a-z]' | sed 's/.*module *//' | tr -d '\r' | sed 's/$/.mod/' ` # name of module file(s) #### FAILS IF NO MODULE, DOES IT WORK FOR 2+? - u=`sed 's/!.*//' $F | egrep -i "^ *use " | sed 's/\ *[uU][sS][eE]\ *\([a-zA-Z_0-9]*\).*/\1.mod/' | tr '[A-Z]' '[a-z]' | egrep -v "mpi.mod|iso_fortran_env.mod" | sort | uniq ` # list of modules used - if [ ${#m} -ne 0 ]; then - o2mod["$o"]=$m - u=`echo $u | sed s:$m::g` - fi - if [ ${#u} -ne 0 ]; then o2use["$o"]=$u; fi - H=$(cpp -E -MM $I $F | tr -d '\n' | sed 's:\\::g') # line of form a.o: a.F b.h c.h ... - o2H["$o"]=$H - h=`echo ${H} | cut -d\ -f3- ` # header files - if [ ${#h} -ne 0 ]; then o2head["$o"]=$h; fi - i=`dirname _ignore_/ignore ${h} | grep -v _ignore_ | sort | uniq | sed 's:^:-I:'` # includes for compilation - if [ ${#i} -ne 0 ]; then o2inc["$o"]=$i; fi - p=`egrep -i "^ *program " $F | awk '{print $2}'` # name of program if any - if [ $librarymode -eq 0 ]; then - O+=($o) # List of all objects - if [ ${#p} -ne 0 ]; then p2o["$p"]=$o; o2p["$o"]=$p; fi - else - if [ ${#p} -eq 0 ]; then O+=($o); fi - fi - if [ ${#m} -ne 0 ]; then - for mm in $m; do all_modules["$mm"]=1; done - else - if [ ${#p} -eq 0 ]; then - externals+=($o) - fi - fi -done - -# Augment with C files -A=$(find -L ${SRC_DIRS} -name "*.c" ) # all C source files -declare -A o2c -OC=() -for F in ${A}; do # F is the relative path to source file - f=`basename $F` # file name stripped of path - o=${f/.c/}.o # object file name - o2c["$o"]=$F - H=$(cpp -E -MM $I $F | tr -d '\n' | sed 's:\\::g') # line of form a.o: a.F b.h c.h ... - o2H["$o"]=$H - h=`echo ${H} | cut -d\ -f3- ` # header files - if [ ${#h} -ne 0 ]; then o2head["$o"]=$h; fi - i=`dirname _ignore_/ignore ${h} | grep -v _ignore_ | sort | uniq | sed 's:^:-I:'` # includes for compilation - if [ ${#i} -ne 0 ]; then o2inc["$o"]=$i; fi - OC+=($o) # List of all objects - #externals+=($o) -done - -if [[ "$executable" == *\.a ]]; then - lib=$executable -else - lib="" - if [ -n "$executable" ]; then - if [ ${#p2o[@]} -eq 0 ]; then - echo 'Error: Option "-p' $executable'"' provided but no programs are present. - exit 1 - elif [ ${#p2o[@]} -eq 1 ]; then # rename executable - p="${o2p[@]}" - o=${p2o[@]} - unset p2o["$p"] - p2o["$executable"]=$o - o2p["$o"]=$executable - else - echo 'Error: Option "-p' $executable'"' cannot be used when multiple programs are present. - exit 1 - fi - fi -fi - -# Write the new makefile -rm -f ${makefile} -echo "#" ${makefile} "created by makedep" >> ${makefile} -echo >> ${makefile} -echo "all:" $lib ${!p2o[@]} >> ${makefile} -echo >> ${makefile} - -echo "# SRC_DIRS is usually set in the parent Makefile but in case is it not we" >> ${makefile} -echo "# record it here from when makedep was previously invoked." >> ${makefile} -echo "SRC_DIRS ?= ${SRC_DIRS}" >> ${makefile} -echo >> ${makefile} - -# Write rule for each object from Fortran -for o in ${O[@]}; do - F=${o2src["$o"]} # source file - m=${o2mod["$o"]} # modules produced with object file - u=${o2use["$o"]} # modules used/needed by object file - H=${o2H["$o"]} # basic C-style rule produced by cpp - i=${o2inc["$o"]} # -I paths needed at compilation - U=() # modules used that are in source tree - NU=() # modules used that were not found in source tree - if [ ${#u} -ne 0 ]; then - for uu in ${u}; do - if [[ ${all_modules["$uu"]} ]]; then - U+=($uu) # source for used module was found - else - NU+=($uu) # did not find source for module - fi - done - fi - if [ $debug -eq 1 ]; then - h=${o2head["$o"]} - p=${o2p["$o"]} - echo "# Source file" $F "produces:" >> ${makefile} - echo "# object:" $o >> ${makefile} - echo "# modules:" $m >> ${makefile} - echo "# uses:" $u >> ${makefile} - echo "# found:" ${U[@]} >> ${makefile} - echo "# missing:" ${NU[@]} >> ${makefile} - echo "# includes:" $h >> ${makefile} - echo "# incpath:" $i >> ${makefile} - echo "# program:" $p >> ${makefile} - fi - - if [ ${#m} -ne 0 ]; then - if [ ${#NU[@]} -ne 0 ]; then - echo "# Note:" $o "uses modules not found the search path:" ${NU[@]} >> ${makefile} - fi - echo $m":" $o >> ${makefile} # a.mod: a.o - fi - echo $H ${U[@]} >> ${makefile} # a.mod a.o: a.F b.mod - echo -e '\t'$compile_line ${i} >> ${makefile} # compile rule -done - -# Write rule for each object from C -for o in ${OC[@]}; do - F=${o2c["$o"]} # source file - H=${o2H["$o"]} # basic C-style rule produced by cpp - i=${o2inc["$o"]} # -I paths needed at compilation - if [ $debug -eq 1 ]; then - h=${o2head["$o"]} - echo "# Source file" $F "produces:" >> ${makefile} - echo "# object:" $o >> ${makefile} - echo "# includes:" $h >> ${makefile} - echo "# incpath:" $i >> ${makefile} - fi - echo $H ${U[@]} >> ${makefile} # a.mod a.o: a.F b.mod - echo -e '\t$(CC) $(DEFS) $(CPPFLAGS) $(CFLAGS) -c $<' ${i} >> ${makefile} # compile rule -done - -if [ ${#lib} -ne 0 ]; then # rule to build library - echo >> ${makefile} - echo $lib: ${O[@]} ${OC[@]} >> ${makefile} - echo -e '\t$(AR) $(ARFLAGS) $@ $^' >> ${makefile} # archive rule -fi - -if [ ${#p2o[@]} -ne 0 ]; then # write rules for linking executables - echo >> ${makefile} - echo "# Note: The following object files are not associated with modules so we assume we should link with them:" ${externals[@]} >> ${makefile} - - echo >> ${makefile} - for p in ${!p2o[@]}; do # p is the executable name - o=${p2o[$p]} - l=$(make -f ${makefile} -B -n -t $o | egrep "\.o$" | sed 's:touch ::' | sort) - echo $p: $l ${externals[@]} >> ${makefile} - echo -e '\t$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)' >> ${makefile} # link rule - done -elif [ -z "$lib" ]; then - echo "Warning: no library target specified (with -x) and no programs found!" - echo "Created target 'obj': use 'make obj' to compile object files." - echo >> ${makefile} - echo "obj: ${O[@]}" >> ${makefile} -fi - -echo >> ${makefile} -echo clean: >> ${makefile} - echo -e '\trm -rf' ${!p2o[@]} $lib '*.o *.mod' >> ${makefile} # compile rule - -echo >> ${makefile} -echo "remakedep: # re-invoke makedep" >> ${makefile} -echo -e '\t' $0 -o ${makefile} '$(SRC_DIRS)' >> ${makefile} +#!/usr/bin/env python + +from __future__ import print_function + +import argparse +import glob +import os +import re +import sys # used only to get path to current script + +# Pre-compile re searches +re_module = re.compile(r"^ *module +([a-z_0-9]+)") +re_use = re.compile(r"^ *use +([a-z_0-9]+)") +re_cpp_include = re.compile(r"^ *# *include *[<\"']([a-zA-Z_0-9\.]+)[>\"']") +re_f90_include = re.compile(r"^ *include +[\"']([a-zA-Z_0-9\.]+)[\"']") +re_program = re.compile(r"^ *[pP][rR][oO][gG][rR][aA][mM] +([a-zA-Z_0-9]+)") + +def create_deps(src_dirs, makefile, debug, exec_target, fc_rule, link_externals, script_path): + """Create "makefile" after scanning "src_dis".""" + + # Scan everything Fortran related + all_files = find_files(src_dirs) + + # Lists of things + # ... all F90 source + F90_files = [f for f in all_files if f.endswith('.f90') or f.endswith('.F90')] + # ... all C source + c_files = [f for f in all_files if f.endswith('.c')] + + # Dictionaries for associating files to files + # maps basename of file to full path to file + f2F = dict( zip( [os.path.basename(f) for f in all_files], all_files ) ) + # maps basename of file to directory + f2dir = dict( zip( [os.path.basename(f) for f in all_files], [os.path.dirname(f) for f in all_files] ) ) + + # Check for duplicate files in search path + if not len(f2F) == len(all_files): + a = [] + for f in all_files: + if os.path.basename(f) in a: + print('Warning: File %s was found twice! One is being ignored but which is undefined.'%(os.path.basename(f))) + a.append( os.path.basename(f) ) + + # maps object file to F90 source + o2F90 = dict( zip( [ object_file(f) for f in F90_files ], F90_files ) ) + # maps object file to C source + o2c = dict( zip( [ object_file(f) for f in c_files ], c_files ) ) + + o2mods, o2uses, o2h, o2inc, o2prg, prg2o, mod2o = {}, {}, {}, {}, {}, {}, {} + externals, all_modules = [], [] + for f in F90_files: + mods, used, cpp, inc, prg = scan_fortran_file( f ) + # maps object file to modules produced + o2mods[ object_file(f) ] = mods + # maps module produced to object file + for m in mods: + mod2o[ m ] = object_file(f) + # maps object file to modules used + o2uses[ object_file(f) ] = used + # maps object file to .h files included + o2h[ object_file(f) ] = cpp + # maps object file to .inc files included + o2inc[ object_file(f) ] = inc + # maps object file to executables produced + o2prg[ object_file(f) ] = prg + if prg: + for p in prg: + if p in prg2o.keys(): + #raise ValueError("Files %s and %s both create the same program '%s'"%( + # f,o2F90[prg2o[p]],p)) + print("Warning: Files %s and %s both create the same program '%s'"%( + f,o2F90[prg2o[p]],p)) + o = prg2o[ p ] + del prg2o[ p ] + #del o2prg[ o ] - need to keep so modifying instead + o2prg[ o ] = [ '[ignored %s]'%(p) ] + else: + prg2o[ p ] = object_file(f) + if not mods and not prg: + externals.append( object_file(f) ) + all_modules += mods + + for f in c_files: + _, _, cpp, inc, _ = scan_fortran_file( f ) + # maps object file to .h files included + o2h[ object_file(f) ] = cpp + + # Are we building a library, single or multiple executables? + targ_libs = [] + if exec_target: + if exec_target.endswith('.a'): + targ_libs.append( exec_target ) + else: + if len(prg2o.keys()) == 1: + o = prg2o.values()[0] + del prg2o[ o2prg[o][0] ] + prg2o[ exec_target ] = o + o2prg[ o ] = exec_target + else: + raise ValueError("Option -x specified an executable name but none or multiple programs were found") + targets = [ exec_target ] + else: + if len(prg2o.keys()) == 0: + print("Warning: No programs were found and -x did not specify a library to build") + targets = prg2o.keys() + + # Create new makefile + with open(makefile, 'w') as file: + print("# %s created by makedep"%(makefile), file=file) + print("", file=file) + print("# Invoked as", file=file) + print('# '+' '.join(sys.argv), file=file) + print("", file=file) + print("all:", " ".join( targets ), file=file) + print("", file=file) + + #print("# SRC_DIRS is usually set in the parent Makefile but in case is it not we", file=file) + #print("# record it here from when makedep was previously invoked.", file=file) + #print("SRC_DIRS ?= ${SRC_DIRS}", file=file) + #print("", file=file) + + #print("# all_files:", ' '.join(all_files), file=file) + #print("", file=file) + + # Write rule for each object from Fortran + for o in sorted( o2F90.keys() ): + found_mods = [m for m in o2uses[o] if m in all_modules] + missing_mods = [m for m in o2uses[o] if m not in all_modules] + incs = nested_inc( o2h[o] + o2inc[o], f2F ) + incdeps = sorted( set( [ f2F[f] for f in incs if f in f2F ] ) ) + incargs = sorted( set( [ '-I'+os.path.dirname(f) for f in incdeps ] ) ) + if debug: + print("# Source file %s produces:"%(o2F90[o]), file=file) + print("# object:", o, file=file) + print("# modules:", ' '.join(o2mods[o]), file=file) + print("# uses:", ' '.join(o2uses[o]), file=file) + print("# found:", ' '.join(found_mods), file=file) + print("# missing:", ' '.join(missing_mods), file=file) + print("# includes_all:", ' '.join(incs), file=file) + print("# includes_pth:", ' '.join(incdeps), file=file) + print("# incargs:", ' '.join(incargs), file=file) + print("# program:", ' '.join(o2prg[o]), file=file) + if o2mods[o]: + print(' '.join(o2mods[o])+':',o, file=file) + print(o+':', o2F90[o], ' '.join(incdeps+found_mods), file=file) + print('\t'+fc_rule, ' '.join(incargs), file=file) + + # Write rule for each object from C + for o in sorted( o2c.keys() ): + incdeps = sorted( set( [ f2F[h] for h in o2h[o] if h in f2F ] ) ) + incargs = sorted( set( [ '-I'+os.path.dirname(f) for f in incdeps ] ) ) + if debug: + print("# Source file %s produces:"%(o2c[o]), file=file) + print("# object:", o, file=file) + print("# includes_all:", ' '.join(o2h[o]), file=file) + print("# includes_pth:", ' '.join(incdeps), file=file) + print("# incargs:", ' '.join(incargs), file=file) + print(o+':', o2c[o], ' '.join(incdeps), file=file) + print('\t$(CC) $(DEFS) $(CPPFLAGS) $(CFLAGS) -c $<', ' '.join(incargs), file=file) + + # Externals (so called) + if link_externals: + print("", file=file) + print("# Note: The following object files are not associated with modules so we assume we should link with them:", file=file) + print("# ", ' '.join(externals), file=file) + o2x = None + else: + externals = [] + + # Write rules for linking executables + for p in sorted( prg2o.keys() ): + o = prg2o[p] + print("", file=file) + print(p+':',' '.join( link_obj(o, o2uses, mod2o, all_modules) + externals ), file=file ) + print('\t$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)', file=file) + + # Write rules for building libraries + for l in sorted( targ_libs ): + print("", file=file) + print(l+':',' '.join( list(o2F90.keys()) + list(o2c.keys()) ), file=file ) + print('\t$(AR) $(ARFLAGS) $@ $^', file=file) + + # Write cleanup rules + print("", file=file) + print("clean:", file=file) + print('\trm -f *.mod *.o', ' '.join(list(prg2o.keys()) + targ_libs), file=file) + + # Write re-generation rules + print("", file=file) + print("remakedep:", file=file) + print('\t'+' '.join(sys.argv), file=file) + +def link_obj(obj, o2uses, mod2o, all_modules): + """List of all objects needed to link "obj",""" + def recur(obj, depth=0): + if obj not in olst: + olst.append( obj) + else: + return + uses = [m for m in o2uses[obj] if m in all_modules] + if len(uses)>0: + ouses = [mod2o[m] for m in uses] + for m in uses: + o = mod2o[m] + recur(o, depth=depth+1) + #if o not in olst: + # recur(o, depth=depth+1) + # olst.append( o ) + return + return + olst = [] + recur(obj) + return sorted( set( olst) ) + +def nested_inc(inc_files, f2F): + """List of all files included by "inc_files", either by #include or F90 include.""" + def recur(hfile): + if hfile not in f2F.keys(): + return + _, _, cpp, inc, _ = scan_fortran_file( f2F[hfile] ) + if len(cpp)+len(inc)>0: + for h in cpp+inc: + if h not in hlst and h in f2F.keys(): + recur(h) + hlst.append( h ) + return + return + hlst = [] + for h in inc_files: + recur(h) + return inc_files + sorted( set( hlst ) ) + +def scan_fortran_file(src_file): + """Scan the Fortran file "src_file" and return lists of module defined, module used, and files included.""" + module_decl, used_modules, cpp_includes, f90_includes, programs = [], [], [], [], [] + with open(src_file, 'r') as file: + lines = file.readlines() + for line in lines: + match = re_module.match( line.lower() ) + if match: + if match.group(1) not in 'procedure': # avoid "module procedure" statements + module_decl.append( match.group(1) ) + match = re_use.match( line.lower() ) + if match: + used_modules.append( match.group(1) ) + match = re_cpp_include.match( line ) + if match: + cpp_includes.append( match.group(1) ) + match = re_f90_include.match( line ) + if match: + f90_includes.append( match.group(1) ) + match = re_program.match( line ) + if match: + programs.append( match.group(1) ) + used_modules = [m for m in sorted(set(used_modules)) if m not in module_decl] + return add_suff(module_decl, '.mod'), add_suff( used_modules, '.mod'), cpp_includes, f90_includes, programs + #return add_suff(module_decl, '.mod'), add_suff( sorted(set(used_modules)), '.mod'), cpp_includes, f90_includes, programs + +def object_file(src_file): + """Return the name of an object file that results from compiling src_file.""" + return os.path.splitext( os.path.basename( src_file ) )[0] + '.o' + + +def find_files(src_dirs): + """Return sorted list of all source files starting from each directory in the list "src_dirs".""" + files = [] + for path in src_dirs: + if not os.path.isdir(path): + raise ValueError("Directory '%s' was not found"%(path)) + for p, d, f in os.walk( os.path.normpath(path), followlinks=True): + for file in f: + if file.endswith('.F90') or file.endswith('.f90') or file.endswith('.h') or file.endswith('.inc') or file.endswith('.c'): + files.append(p+'/'+file) + return sorted( set( files ) ) + +def add_suff(lst, suff): + """Add "suff" to each item in the list""" + return [ f+suff for f in lst ] + +# Parse arguments +parser = argparse.ArgumentParser( + description="Generate make dependencies for F90 source code.") +parser.add_argument('path', nargs='+', + help="Directories to search for source code.") +parser.add_argument('-o', '--makefile', default='Makefile.dep', + help="Name of Makefile to put dependencies in to. Default is Makefile.dep.") +parser.add_argument('-f', '--fc_rule', default="$(FC) $(DEFS) $(FCFLAGS) $(CPPFLAGS) -c $<", + help="""String to use in the compilation rule. Default is: + '$(FC) $(DEFS) $(FCFLAGS) $(CPPFLAGS) -c $<'""") +parser.add_argument('-x', '--exec_target', + help="""Name of executable to build. + Fails if more than one program is found. + If EXEC ends in .a then a library is built.""") +parser.add_argument('-e', '--link_externals', action='store_true', + help="Always compile and link any files that do not produce modules (externals).") +parser.add_argument('-d', '--debug', action='store_true', + help="Annotate the makefile with extra information.") +args = parser.parse_args() + +# Do the thing +create_deps(args.path, args.makefile, args.debug, args.exec_target, args.fc_rule, args.link_externals, sys.argv[0])