Source code for makemake90

#!/usr/bin/env python3

'''Usage: makemake90 [src=...] [obj=...] [mod=...] [bin=...]

Optional arguments specify places for source (.f90), object (.o), module
(.mod), and executable files. They all default to the currect directory.
There is no need to specify them again when updating existing makefiles.
'''

__version__ = '0.3'

import os
import re
import sys

[docs] def parameters(*filenames, **args): """Fetch parameters from existing makefile and command arguments. Parameters ---------- *filenames : str Possible names of makefile. **args : str Default command arguments. Returns ------- str Name of makefile. str Static makefile header. str Static makefile footer. dict of str Command arguments. """ if not filenames: filenames = ['GNUmakefile', 'makefile', 'Makefile'] preamble = '' epilogue = '' for filename in filenames: if os.path.exists(filename): with open(filename) as makefile: for line in makefile: if 'generated by makemake' in line: for arg in re.findall(r'\w+=[^\s:=]+', line): sys.argv.insert(1, arg) break preamble += line else: raise SystemExit('Unknown Makefile already exists') for line in makefile: if 'not generated by makemake' in line: break for line in makefile: epilogue += line break for arg in sys.argv[1:]: if '=' in arg: key, value = arg.split('=') args[key] = value else: raise SystemExit(__doc__.rstrip()) preamble = preamble.strip() epilogue = epilogue.strip() return filename, preamble, epilogue, args
[docs] def dependencies(src='.', obj='.', bin='.', **ignore): """Determine dependencies of Fortran project. Parameters ---------- src : str, default '.' Directory with source files. obj : str, default '.' Directory for object files. bin : str, default '.' Directory for executable binary files. Returns ------- dict of set Programs with direct and indirect dependencies. dict of set Objects with direct dependencies. set All objects used. """ references = {} companions = {} components = {} folders = [src] def preprocess(lines): for line in lines: line = re.sub('!.*', '', line).strip() if line: yield line for folder in folders: for file in os.listdir(folder): if file.startswith('.'): continue path = folder + '/' + file if os.path.isdir(path): folders.append(path) elif path.endswith('.f90'): doto = re.sub('^%s/' % src, '%s/' % obj, path) doto = re.sub(r'\.f90$', '.o', doto) references[doto] = set() in_module = False with open(path) as code: code = preprocess(code) for line in code: while line.endswith('&'): line = line.rstrip('&') line += next(code).lstrip('&') match = re.match(r'(use(?:\b.*::)?|program|module)' r'\s+(\w+)\s*(?:$|,)', line, re.I) if match: statement, name = match.groups() if statement == 'use': references[doto].add(name) elif statement == 'module': companions[name] = doto in_module = True elif statement == 'program': components['%s/%s' % (bin, name.replace('_dot_', '.'))] = {doto} match = re.match(r'.*\bexternal\b.*::(.*)', line, re.I) if match: for name in re.findall(r'\w+', match.group(1)): references[doto].add(name) if in_module: if re.match('end module', line, re.I): in_module = False else: match = re.match('(subroutine|function)' r'\s+(\w+)', line, re.I) if match: companions[match.group(2)] = doto for target, modules in references.items(): references[target] = set(companions[name] for name in modules if name in companions) related = set() for doto in components.values(): todo = list(doto) for target in todo: new = references[target] - doto doto |= new todo += new related |= doto for target in list(references.keys()): if target not in related: del references[target] else: references[target].discard(target) return components, references, related
[docs] def makefile(filename='Makefile', components={}, references={}, related=None, preamble='', epilogue='', src='.', obj='.', mod='.', bin='.', **ignore): """Create makefile. Parameters ---------- filename : str, default 'Makefile' Name of makefile. components : dict of set, default {} Programs with direct and indirect dependencies. references : dict of set, default {} Objects with direct dependencies. related : set, default None All objects used. Inferred from `components` if absent. preamble : str, default '' Static makefile header. epilogue : str, default '' Static makefile footer. src : str, default '.' Directory with source files. obj : str, default '.' Directory for object files. mod : str, default '.' Directory for module files. bin : str, default '.' Directory for executable binary files. """ if related is None: related = set().union(*components.values()) args = dict(src=src, obj=obj, mod=mod, bin=bin) def join(these): return ' '.join(sorted(these)) programs = join(components) adjuncts = join(related) def listing(dependencies): return '\n'.join(target + ': ' + join(doto) for target, doto in sorted(dependencies.items()) if doto) components = listing(components) references = listing(references) arguments = ''.join(' %s=%s' % (key, value) for key, value in sorted(args.items()) if value != '.') modules_flag = '' if mod == '.' else ''' modules_gfortran = -J{0} modules_ifort = -module {0} modules_ifx = ${{modules_ifort}} override FFLAGS += ${{modules_$(FC)}} '''.format(mod) content = ''' # generated by makemake90{arguments}: {modules_flag} needless += {adjuncts} {mod}/*.mod programs = {programs} .PHONY: all clean cleaner all: $(programs) clean: \trm -f $(needless) cleaner: clean \trm -f $(programs) $(programs): \t$(FC) $(FFLAGS) -o $@ $^ $(LDLIBS) {obj}/%.o: {src}/%.f90 \t$(FC) $(FFLAGS) -c $< -o $@ {components} {references} '''.format(**vars()) content = re.sub(r'(^|\s)\./', r'\1', content) with open(filename, 'w') as makefile: if preamble: makefile.write(preamble) else: makefile.write(''' FC = gfortran flags_gfortran = -std=f2008 -pedantic -Wall -Wno-maybe-uninitialized flags_ifort = -O0 -stand f08 -warn all flags_ifx = ${flags_ifort} FFLAGS = ${flags_$(FC)} # dependent_program: LDLIBS = -llapack -lblas '''.strip()) makefile.write(content) if epilogue: makefile.write(''' # not generated by makemake90: {epilogue} '''.format(**vars())) for mkdir in set(args.values()) | set(map(os.path.dirname, related)): if not os.path.isdir(mkdir): os.makedirs(os.path.realpath(mkdir)) print('Created folder "%s" required to make project' % os.path.normpath(mkdir))
def main(): filename, preamble, epilogue, args = parameters() components, references, related = dependencies(**args) makefile(filename, components, references, related, preamble, epilogue, **args) if __name__ == '__main__': main()