diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aee2e4c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.mk diff --git a/README.md b/README.md index aab49e8..98dc092 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,69 @@ Original script by D. Dickinson Usage ===== -Here's an example of how to use `fort_depend.py` in your makefiles: +Here's an example of how to use `fort_depend.py` in your makefiles. + +Input parameters: + + + -f --files Files to process + + -o --output Output file + + -v -vv -vvv Different level of verbosity contained in standard output + + -w --overwrite Overwrite output file without warning + + -r --root_dir Project root directory + + -d --dep_dir List of selected dependecy directories + + +For bigger project example, look at Makefiles, rule.mk, src_dir_path.mk and other files in fll project at +https://github.com/libm3l/fll which is a linked list library written in Fortran and which uses this +script to get fortran project dependencies + +========================= MAKEFILE EXAMPLE =================== # Script to generate the dependencies - MAKEDEPEND=/path/to/fort_depend.py + # for example - look into www.github.com/libm3l/fll project + # + # specify location of fort_depend.py script + # + MAKEDEPEND=/path_to/fort_depend.py + # + # The PROJ_ROOT_PATH specifies the common directory for all the project subdirectories + # specify location of fortran project root directory + # the script can now make dependencies for project with source + # files located in different subdirectories directories in the project root directory. + # The script lops over all of them finding dependencies for all fortran files + # use option --root_dir + # + PROJ_ROOT_PATH =/path/to_proj_root_dir + # + # specify selected path for dependency search, ie. for big projects, rather then search through all directories in PROJ_ROOT_PATH + # you can specify those directories which will be searched for dependencies are located + # if not specified, the script will be searching through entire tree. For bigger projects this may lead to longer time while + # looking for dependencies + # use option --dep_dir + # # $(DEP_FILE) is a .dep file generated by fort_depend.py DEP_FILE = my_project.dep # Source files to compile - OBJECTS = mod_file1.f90 \ - mod_file2.f90 + # You can specify all .f90 files + FFILES= $(notdir $(wildcard $(srcdir)/*.f90)) + # or you can specify names of the files directly + FFILES = mod_file1.f90 \ + mod_file2.f90 + + # + # files specified in FFILES are dependent on files located in directory ../data_util and ../../util + # if not specified, the sript looks for all files in PROJ_ROOT_PATH + FMODDIRS= \ + ../data_util \ + ../../util \ # Make sure everything depends on the .dep file all: $(actual_executable) $(DEP_FILE) @@ -31,6 +83,6 @@ Here's an example of how to use `fort_depend.py` in your makefiles: # when you change your source $(DEP_FILE): $(OBJECTS) @echo "Making dependencies!" - cd $(SRCPATH) && $(MAKEDEPEND) -w -o /path/to/$(DEP_FILE) -f $(OBJECTS) + cd $(srcdir) && $(MAKEDEPEND) -r $(PROJ_ROOT_PATH) -d $(FMODDIRS) -v w -o $(srcdir)/$(DEP_FILE) -f $(FFILES) - include $(DEP_FILE) + -include $(DEP_FILE) diff --git a/fort_depend.py b/fort_depend.py index 60484dc..79c316c 100755 --- a/fort_depend.py +++ b/fort_depend.py @@ -1,33 +1,137 @@ #!/usr/bin/python +#The MIT License (MIT) + +#Copyright (c) 2014 David Dickinson, Peter Hill + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in all +#copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +#SOFTWARE. +# +# +# This is a modification of the original script of D Dickinson @https://github.com/ZedThree/fort_depend.py +# done by Adam Jirasek +# The modified version can be found @https://github.com/libm3l/fort_depend.py +# +# This is a script which makes fortran project dependecies +# Fortran project can have source files located in diffrent subdirectories +# where the common directory (project root directory)is specified as an input parameter with an option -r +# The script is executed in each directory separately and creates a project.dep file with fortran dependencies +# however, while searching modules it will loop over all subdirectories in project root directory +# +# If user wants to specify the search for modules to selected set of subdirectories, +# he/she can use --dep_dir followed by list of selected directories +# +# If -r not specified, the script will use the current working directory and it will search +# for modules in this directory only. +# +# List of all options is: +# -f --files Files to process +# -o --output Output file +# -v -vv -vvv Different level of verbosity contained in standard output +# -w --overwrite Overwrite output file without warning +# -r --root_dir Project root directory +# -d --dep_dir List of selected dependecy directories +# +# For example of how the fortran dependency scritp can be used for larger project see +# FLL linked list utility at https://github.com/libm3l/fll +# +# History: +# Version Date Author Patch number CLA Comment +# ------- -------- -------- ------------ --- ------- +# 1.1 01/09/17 Adam Jirasek Rewrite original version +# +# +# +# import os import re +import glob +import fnmatch +import os +import sys +from time import gmtime, strftime +import getpass +from datetime import datetime +import time, sys #Definitions -def run(files=None,verbose=True,overwrite=None,output=None,macros={},build=''): - l=create_file_objs(files,macros) +def run(path,dep=None,ignore=None,files=None,verbose=None,overwrite=None,output=None,macros={},build=''): + + cwd = os.getcwd() +# +# if project root path not specified, used current directory +# + if(path == None): + path = os.getcwd() + + path = check_path(path=path) + cwd = check_path(path=cwd) + + if int(verbose) > 0: + print(" ") + print("\033[031m Making dependencies in \033[032m"+cwd+"\033[039m directory") + print(" ") + else: + print(" ") +# +# get files where to look for modules +# if list of preferred directories is specified in dep +# list only these files, otherwise +# list all file in path dir +# +# files paths is relative to projet root directory path so that if the compillation is done in different directory then +# where the source files are located, there are no any prolems with it +# + ff=get_all_files(path=path, dep=dep) + + if int(verbose) > 2: + print(" ") + print("\033[031m Searching for modules in files:\033[039m") + print(ff) + + l=create_file_objs( (verbose) ,files,macros) mod2fil=file_objs_to_mod_dict(file_objs=l) - depends=get_depends(fob=l,m2f=mod2fil) +# +# make dependencies +# + depends=get_depends(ignore=ignore,verbose=verbose,cwd=cwd,fob=l,m2f=mod2fil,ffiles=ff) - if verbose: - for i in depends.keys(): - print "\033[032m"+i+"\033[039m depends on :\033[034m" - for j in depends[i]: print "\t"+j - print "\033[039m" + if int(verbose) == 3 : + for i in depends.keys(): + print ("\033[032m "+i+"\033[039m depends on:\033[034m") + for j in depends[i]: print( "\t"+j) + print ("\033[039m") if output is None: output = "makefile.dep" - tmp=write_depend(outfile=output,dep=depends,overwrite=overwrite,build=build) + tmp=write_depend(verbose=verbose,path=path,cwd=cwd,outfile=output,dep=depends,overwrite=overwrite,build=build) return depends -def write_depend(outfile="makefile.dep",dep=[],overwrite=False,build=''): +def write_depend(verbose,path,cwd,outfile="makefile.dep",dep=[],overwrite=False,build=''): "Write the dependencies to outfile" - #Test file doesn't exist +# +#Test file doesn't exist +# if os.path.exists(outfile): if not(overwrite): - print "\033[031mWarning file exists.\033[039m" + print ("\033[031mWarning file exists.\033[039m") opt=raw_input("Overwrite? Y... for yes.") else: opt="y" @@ -35,53 +139,284 @@ def write_depend(outfile="makefile.dep",dep=[],overwrite=False,build=''): pass else: return - - #Open file +# +#Open file +# + if int(verbose) > 1: + print(" ") + print("\033[031m Opening dependency file \033[032m"+outfile+"\033[039m ...") + print(" ") f=open(outfile,'w') - f.write('# This file is generated automatically. DO NOT EDIT!\n') + f.write('# This file is generated automatically by fort_depend.py. DO NOT EDIT!\n') +# +# header +# + username = getpass.getuser() + f.write("#\n") + f.write("# Created by: "+username+"\n") +# f.write("# Date: "+strftime("%Y-%m-%d %H:%M:%S", gmtime()) + "\n") + f.write("# Date: "+ datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "\n") + f.write("#\n") + for i in dep.keys(): tmp,fil=os.path.split(i) +# +# get name of file for which you write dependencies with .o +# stri="\n"+os.path.join(build, fil.split(".")[0]+".o"+" : ") - for j in dep[i]: - tmp,fil=os.path.split(j) - stri=stri+" \\\n\t"+os.path.join(build, fil.split(".")[0]+".o") + if int(verbose) > 1: + print("\033[031m Writing dependency info for \033[032m"+i+"\033[039m module") +# +# now write down all files containing modules +# the list contains file names with .f or f90 suffix, replace by .o suffix + if not(dep[i] == ""): +# +# add module name to stri and separate by new line and tab +# + for j in dep[i]: + + npathseg = j.count('/') + if npathseg == 0: +# +# module is in the file located in the same directory as the file for which the dependency is written +# + tmp,fil=os.path.split(j) +# +# replace suffix with .o +# + fil= os.path.splitext(j)[0]+'.o' + else: +# +# module is in file located in different directory +# + fil = j + + if "../" in fil: + stri = stri + " \\\n\t" + fil + else: + stri=stri+" \\\n\t"+os.path.join(build, fil.split(".")[0]+".o") +# +# add the last new line and write to a file +# stri=stri+"\n" f.write(stri) + f.close() + + if int(verbose) > 1: + print("\033[031m Finished ... \033[039m") + print(" ") + return -def get_source(ext=[".f90",".F90"]): - "Return all files ending with any of ext" +def get_source(ext=[".f90",".F90",".f",".F"]): +# +# "Return all files ending with any of ext" +# tmp=os.listdir(".") + fil=[] for i in ext: fil.extend(filter(lambda x: x.endswith(i),tmp)) + return fil -def create_file_objs(files=None, macros={}): +def get_all_files(path,dep): +# +# list all fortran files where to look for possible module +# once found add their relative path from this directory to project root directory +# it is important to do so when the project is compiled in a different directory +# the path of all modules should be relatie +# + matches = [] +# +# get relative path of current directory +# + currdirr = os.getcwd() + currdirr = check_path(path=currdirr) + relapth = currdirr + relapth=relapth.replace(path,'') + slsh_count = relapth.count('/') + relapth = '' + for isl in range(0, slsh_count): + relapth += "../" +# +# list only files located in those +# + files = 0 + + + if not(dep == None): + dep.append(currdirr) + + for i in dep: +# +# use basolute path to preferred directories ie.: os.path.abspath(i) +# + for root, dirnames, filenames in os.walk(os.path.abspath(i)): +# +# list all files and check if they end up with given suffix, if yes, add to the list +# + for filename in filenames: + if filename.endswith(('.f', '.f90', '.F', '.F90')): +# files = files + 1 +# status(files) + +## matches.append(os.path.join(root, filename)) +## +## add specified dependency directory location (i) rather then aboslute path +## + if(root == currdirr): +# +# file is in this directory add juts its name +# + matches.append(filename) + else: +# +# file is different directory, +# substract project root parth from the file path +# add trailing / and then add ../ to get to project root path, ie..file will have relative path +# + cwurrdirr = root + cwurrdirr = cwurrdirr.replace(path,'') +# +# if specified directory is a subdirectory in projet root path then add files +# otherwise not (it is possible then some external library is specified) + if root != cwurrdirr: + cwurrdirr = relapth + cwurrdirr + "/" + matches.append(os.path.join(cwurrdirr, filename)) +# +# otherwise include all files from path dir +# + else: +# +# loop over all file in project root path directory +# + for root, dirnames, filenames in os.walk(path): + + for filename in filenames: + if filename.endswith(('.f', '.f90', '.F', '.F90')): + +# files = files + 1 +# status(files) + + # matches.append(os.path.join(root, filename)) + + if(root == currdirr): +# +# file is in this directory add juts its name +# + matches.append(filename) + else: +# +# file is different directory, +# sybstract project root parth from the file path +# add trailing / and then add ../ to get to project root path, ie..file will have relative path +# + cwurrdirr = root + cwurrdirr=cwurrdirr.replace(path,'') + cwurrdirr = relapth + cwurrdirr + "/" + matches.append(os.path.join(cwurrdirr, filename)) + + return matches + +def check_if_there(use,file): +# +# "return if you see module name" +# make routine to consider version of python installation +# + list = ['use', 'program', 'end'] + + if sys.version_info < (3,0): + with open(file) as f: + for line in f: + if "module" in line.lower(): + extrline = line.lower() +# +# if line with module contains any of the list word +# do not cinsider this line +# + if any(word in extrline for word in list): + continue + + extrline = extrline.replace("module", "",1) +# +# check that module name on line line (extrline) +# is the same as module name which we are looking fore (defined in USE) +# if yes, we found module, return +# + if use.lower().strip() == extrline.strip(): + f.close() + return 1 + else: + with open(file) as f: + with open(file, errors='ignore') as f: + for line in f: + if "module" in line.lower(): + extrline = line.lower() +# +# if line with module contains any of the list word +# do not cinsider this line +# + if any(word in extrline for word in list): + continue + + extrline = extrline.replace("module", "",1) +# +# check that module name on line line (extrline) +# is the same as module name which we are looking fore (defined in USE) +# if yes, we found module, return +# + if use.lower().strip() == extrline.strip(): + f.close() + return 1 + + f.close() + return 0 + + +def create_file_objs(verbose, files=None, macros={}): l=[] - if files is None: - files = get_source() +# if files is None: +# files = get_source() + + files = get_source() + if int(verbose) > 1 : + print(" ") + print("\033[031m Searching modules for files:\033[039m") + print(" ") + + for i in files: source_file = file_obj() + if int(verbose) > 1 : + print("\033[031m -- \033[039m" + i) source_file.file_name = i source_file.uses = get_uses(i,macros) + source_file.includes = get_includes(i,macros) source_file.contains = get_contains(i) l.append(source_file) + if int(verbose) > 1 : + print(" ") + return l def get_uses(infile=None, macros={}): "Return which modules are used in infile after expanding macros" - p=re.compile("^\s*use\s*(?P\w*)\s*(,)?\s*(only)?\s*(:)?.*?$",re.IGNORECASE).match + p=re.compile("^\s*use\s+(?P\w*)\s*(,)?\s*(only)?\s*(:)?.*?$",re.IGNORECASE).match uses=[] - with open(infile,'r') as f: + if sys.version_info < (3,0): + with open(infile,'r') as f: + t=f.readlines() + else: + with open(infile,'r',errors='ignore') as f: t=f.readlines() for i in t: @@ -99,13 +434,47 @@ def get_uses(infile=None, macros={}): return uniq_mods +def get_includes(infile=None, macros={}): + "Return which modules are included in infile after expanding macros" + p=re.compile("\#include\s*\"(?P\w*).inc\"$",re.IGNORECASE).match + + includes=[] + + if sys.version_info < (3,0): + with open(infile,'r') as f: + t=f.readlines() + else: + with open(infile,'r',errors='ignore') as f: + t=f.readlines() +# with open(infile,'r',encoding = "ISO-8859-1") as f: +# t=f.readlines() + + for i in t: + tmp=p(i) + if tmp: + includes.append(tmp.group('incfile').strip()+".inc") + + # Remove duplicates + uniq_includes = list(set(includes)) + + for i, mod in enumerate(uniq_includes): + for k, v in macros.items(): + if re.match(k, mod, re.IGNORECASE): + uniq_includes[i] = mod.replace(k,v) + + return uniq_includes + def get_contains(infile=None): "Return all the modules that are in infile" p=re.compile("^\s*module\s*(?P\w*)",re.IGNORECASE).match contains=[] - with open(infile,'r') as f: + if sys.version_info < (3,0): + with open(infile,'r') as f: + t=f.readlines() + else: + with open(infile,'r', errors='ignore') as f: t=f.readlines() for i in t: @@ -113,7 +482,7 @@ def get_contains(infile=None): if tmp: contains.append(tmp.group('modname').strip()) - # Remove duplicates before returning +# Remove duplicates before returning return list(set(contains)) def file_objs_to_mod_dict(file_objs=[]): @@ -124,24 +493,111 @@ def file_objs_to_mod_dict(file_objs=[]): dic[j.lower()]=i.file_name return dic -def get_depends(fob=[],m2f=[]): +def get_depends(ignore,verbose,cwd,fob=[],m2f=[], ffiles=[]): deps={} + istat = 0 + files = 0 + for i in fob: + if int(verbose) > 1 : + print("\033[031m Checking dependency for file: \033[032m"+i.file_name+"\033[039m") + + else: + files = files + 1 + status(files) + tmp=[] for j in i.uses: + if ignore and (j in ignore): continue try: +# +# module is in the same directory, include it +# tmp.append(m2f[j.lower()]) - except: - print "\033[031mError\033[039m module \033[032m"+j+"\033[039m not defined in any files. Skipping..." + istat = 1 + except KeyError: +# +# module is not located in the same directory, loop through all other files specified in ffiles +# these are files found in function get_all_files +# + for k in ffiles: + + dir,fil=os.path.split(k) + dir = dir+ "/" + retval = 0 + + if not(cwd.strip() == dir): + + retval=check_if_there(use=j,file=k) + + if retval > 0: + istat = 1 + name=os.path.splitext(k)[0]+'.o' + tmp.append(name.lower()) + + if int(verbose) > 2 : + print ("\033[031m Note: \033[039m module \033[032m"+j+"\033[039m found in \033[032m"+name+"\033[039m file") + print ("\033[031m \033[039m adding this module to the dependency tree \033[032m\033[039m") + break #break loop, dependency declared + + if istat== 0 and (j != ""): + if int(verbose) > 2 : + print("") + print ("\033[031m Note!!!!: \033[039m module \033[032m"+j+"\033[039m not defined in any file") + print ("\033[031m \033[039m assuming intrinsic module, not adding to dependency tree ... \033[032m\033[039m") +# +# once module found, break the loop +# +# break #break loop, dependency declared + if (istat != 0): +# +# if file containign module, add to the list +# + deps[i.file_name]=tmp + else: + deps[i.file_name]="" - deps[i.file_name]=tmp + if int(verbose) > 1 : + print("\033[031m Done ... \033[032m") return deps +def check_path(path): + if path.endswith("/"): + return path + else: + path=path + "/" + + return path + +def get_relative_path_name(file,path,cwd): + length = len(path) + filetmp = file + fil = filetmp.replace(filetmp[:length], '') + + loccwd = cwd + loccwd = loccwd.replace(loccwd[:length], '') + + npathseg = loccwd.count('/') + for x in range(0, npathseg): + fil = "../"+fil + + return fil + +def status(i): + width = i + sys.stdout.write(u"\u001b[1000D") # Move left + if(status > 1): + sys.stdout.write(u"\u001b[" + str(0) + "A") # Move up + +# print "[" + "#" * width + " " * (25 - width) + "]" + + class file_obj: def __init__(self): self.file_name=None self.uses=None + self.includes=None self.contains=None self.depends_on=None @@ -153,13 +609,18 @@ def __init__(self): # Add command line arguments parser = argparse.ArgumentParser(description='Generate Fortran dependencies') parser.add_argument('-f','--files',nargs='+',help='Files to process') + parser.add_argument('-i','--ignore',nargs='+',help='Modules to ignore') parser.add_argument('-D',nargs='+',action='append',metavar='NAME=DESCRIPTION', help="""The macro NAME is replaced by DEFINITION in 'use' statements""") parser.add_argument('-b','--build',nargs=1,help='Build Directory (prepended to all files in output', default='') parser.add_argument('-o','--output',nargs=1,help='Output file') parser.add_argument('-v','--verbose',action='store_true',help='explain what is done') + parser.add_argument('-vv','--vverbose',action='store_true',help='explain what is done') + parser.add_argument('-vvv','--vvverbose',action='store_true',help='explain what is done') parser.add_argument('-w','--overwrite',action='store_true',help='Overwrite output file without warning') + parser.add_argument('-r','--root_dir',nargs=1,help='Project root directory') + parser.add_argument('-d','--dep_dir',nargs='+',action='append',help='Preferred dependecy directory') # Parse the command line arguments args = parser.parse_args() @@ -174,5 +635,20 @@ def __init__(self): output = args.output[0] if args.output else None build = args.build[0] if args.build else '' - - run(files=args.files, verbose=args.verbose, overwrite=args.overwrite, macros=macros, output=output, build=build) + root_dir = args.root_dir[0] if args.root_dir else None + dep_dir = args.dep_dir[0] if args.dep_dir else None + + if not root_dir: + print ("\033[031mError: \033[039m missing path to project root directory \033[032m") + sys.exit() + + verbose = 0 + if(args.verbose): + verbose = 1 + if(args.vverbose): + verbose = 2 + if(args.vvverbose): + verbose = 3 + + + run(path=root_dir, dep=dep_dir, ignore=args.ignore, files=args.files, verbose=verbose, overwrite=args.overwrite, macros=macros, output=output, build=build)