-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfortdep2.py
333 lines (268 loc) · 13.4 KB
/
fortdep2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
#!/usr/bin/env python3
# coding: utf-8
from sys import stdout, stderr
from os import listdir, path, getcwd, walk
import re
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
re_fortext = re.compile(r'\.[fF](90|95|03|08|15|18|)$')
#------------------------------------------------------------------------------#
class SourceFile(object):
def __init__(self, fnsrc):
self.fnsrc = fnsrc
self.fnobj = re.sub(re_fortext, '.o', fnsrc)
self.fnexe = re.sub(re_fortext, '', fnsrc)
def __repr__(self):
return self.fnobj
#------------------------------------------------------------------------------#
# class Dependency(object):
# def __init__(self, L, R):
# self._L = L
# self._R = R
# def __repr__(self):
# return u'<{} depends on {}>'.format(self._L, self._R)
#------------------------------------------------------------------------------#
class Unit(object):
def __init__(self, name, objfile = None):
self.objfile = objfile
self.name = name.lower()
self.deps = set()
self.includes = set()
def assign_object_file(self, objfile):
assert type(objfile) == SourceFile
if self.objfile: raise Exception(u'Module {} is already assigned'
' to {}'.format(self.name, self.objfile))
self.objfile = objfile
def __repr__(self):
return ('program ' if type(self) == Program else '') \
+ self.name + (' /' + self.objfile.fnsrc + '/' if self.objfile else '')
def summ(self):
return str(self) \
+ ('\n depends on ' + str(self.deps) if len(self.deps) > 0 else '') \
+ ('\n includes ' + str(self.includes) if len(self.includes) > 0 else '') \
+ ('\n has submds ' + str(self.submodules) if hasattr(self, 'submodules') and len(self.submodules) > 0 else '')
#------------------------------------------------------------------------------#
class Program(Unit):
def __init__(self, name, objfile):
Unit.__init__(self, name, objfile)
#------------------------------------------------------------------------------#
class Module(Unit):
def __init__(self, name, objfile = None):
Unit.__init__(self, name, objfile)
self.submodules = set()
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
universe = set()
objfiles = set()
#------------------------------------------------------------------------------#
def query_modules(name):
return next(filter(lambda u: (type(u) == Module) \
and (u.name == name.lower()), universe), None)
#------------------------------------------------------------------------------#
def query_modules_or_new(name):
global universe
m = query_modules(name)
if m == None:
m = Module(name)
universe.add(m)
return m
#------------------------------------------------------------------------------#
def walktree(u, bag = set()):
harvest = set((u,))
deps = set(filter(lambda x: x.objfile != None, u.deps))
if hasattr(u, 'submodules'):
deps |= set(filter(lambda x: x.objfile != None, u.submodules))
new_bag = bag | deps | set((u,))
for d in deps:
if not d in bag:
harvest |= walktree(d, new_bag)
return harvest
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
# compile regexp
re_module = re.compile(r'\s*(module|program|submodule\s*\(\s*([a-z0-9_]+)\s*\))\s+([a-z0-9_]+)', re.IGNORECASE)
re_module_end = re.compile(r'^\s*end\s+(module|submodule|program)', re.IGNORECASE)
re_submodule = re.compile(r'submodule\s*\(\s*([a-z0-9_]+)\s*\)', re.IGNORECASE)
re_use = re.compile(r'^\s*use\s+([a-z0-9_]+)', re.IGNORECASE)
re_include = re.compile(r'^\s*include\s+["\'](.+)["\']', re.IGNORECASE)
#------------------------------------------------------------------------------#
verbose = False
#------------------------------------------------------------------------------#
def parse_source(f, objfil):
global universe
# when module name is found, it will be set here
current_module = None
for line in f:
# check if we are outside the module
if current_module == None:
# try to match the module line
mtch = re.match(re_module, line)
# is this a module line? (module, program, etc)
if mtch:
mtype, mparent, mname = mtch.groups()
if mtype.lower() == 'program':
current_module = Program(mname, objfil)
universe.add(current_module)
if verbose: stderr.write(u'+ program {}\n'.format(mname))
elif mtype.lower() == 'module':
# search for blank modules in the universe before adding
current_module = query_modules_or_new(mname)
current_module.assign_object_file(objfil)
if verbose: stderr.write(u'+ module {}\n'.format(mname))
elif mparent != None:
# for submodules, first search if parent is defined
parent_module = query_modules_or_new(mparent)
# search for blank modules in the universe before adding
current_module = query_modules_or_new(mname)
current_module.assign_object_file(objfil)
current_module.deps.add(parent_module)
parent_module.submodules.add(current_module)
if verbose: stderr.write(u'+ module {}, submodule of {}\n'.format(mname, mparent))
else: raise Exception("wtf")
# if not, try to match use statement. it could be anonymous program
# mtch = re.match(re_use, line)
# if mtch:
# log('anonymous program in ' + objfil.fnsrc, 2)
# # create program unit
# current_module = Program('program_' + objfil.fnexe, objfil)
# universe.add(current_module)
# # add the module as dependency
# mdep = query_modules_or_new(mtch.group(1))
# current_module.deps.add(mdep)
# we are inside the module
else:
# two things can happen: "use" statement, include or unit end
# try to match unit end
if re.match(re_module_end, line):
# we are going out of the module
current_module = None
continue
# attempt to match "use"
mtch = re.match(re_use, line)
if mtch:
# use statement matched; add as dependency
mdep = query_modules_or_new(mtch.group(1))
current_module.deps.add(mdep)
if verbose: stderr.write('* {} uses {}\n'.format(current_module, mdep))
continue
# attempt to match "include"
mtch = re.match(re_include, line)
if mtch:
current_module.includes.add(mtch.group(1))
if verbose: stderr.write('* {} includes {}\n'.format(current_module, mtch.group(1)))
continue
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
def parse_cmdline_args():
from sys import argv
from argparse import ArgumentParser
parser = ArgumentParser('fortdep2')
parser.add_argument('--programs', '-p', action = 'store_true',
help = 'generate dependencies for programs')
parser.add_argument('--no-includes', '-i', action = 'store_true',
help = 'don\'t generate dependencies from includes')
parser.add_argument('--scaffold', '-s', action = 'store_true', dest = 'whole',
help = 'generate entire makefile')
parser.add_argument('--verbose', '-v', action = 'store_true',
help = 'more info')
parser.add_argument('--encoding', '-e', type = str, default = 'utf-8',
dest = 'encoding', help = 'specify input encoding (default: utf-8)')
parser.add_argument('--output', '-o',
type = str, default = '--',
help = 'write output to file')
parser.add_argument('path', nargs='*')
return parser.parse_args(argv[1:])
#------------------------------------------------------------------------------#
def check_makefile_vpath():
from subprocess import check_output
mkoutp = check_output([
'make',
'--eval=print_vpath:\n\t@echo $(VPATH)',
'print_vpath',
]).decode().replace('\n','')
return re.split(r'[:\s]', mkoutp) if mkoutp else None
#------------------------------------------------------------------------------#
#------------------------------------------------------------------------------#
def main():
global universe, objfiles, verbose
#--------------------------------------------------------------------------#
# main program starts here: parse command line args
args = parse_cmdline_args()
# if no output file given, write to stdout
output = stdout if args.output == '--' else open(args.output, 'w')
verbose = args.verbose
make_vpath = None
#--------------------------------------------------------------------------#
# scan for files, either from makefile VPATH or just recursively
if len(args.path) == 0:
make_vpath = check_makefile_vpath()
if make_vpath:
stderr.write('found Makefile, using directories: {}\n'.format(", ".join(make_vpath)))
inp = [ (folder, [ f for f in listdir(folder) \
if path.isfile(path.join(folder,f)) ]) \
for folder in make_vpath ]
else:
stderr.write(u'no directories given; scanning recursively...\n')
cwd = getcwd()
inp = [ (path.relpath(fd,cwd),fl) for fd,lf,fl in walk(cwd) ]
else:
inp = [ (folder, [ f for f in listdir(folder) \
if path.isfile(path.join(folder,f)) ]) \
for arg in args.path for folder in arg.split(':') ]
#--------------------------------------------------------------------------#
# scanning files complete, now analyze filenames and parse each foratran source
for reldir, filelist in inp:
for fn in filelist:
if re.search(re_fortext, fn) == None: continue
# relative directory + filename
filepath = path.join(reldir,fn)
with open(filepath, 'r', encoding = args.encoding) as f:
obj = SourceFile(fn)
objfiles.add(obj)
parse_source(f, obj)
#--------------------------------------------------------------------------#
allprograms = set(filter(lambda x: type(x) == Program and x.objfile != None, universe))
#--------------------------------------------------------------------------#
if args.whole:
output.write('# generated by fortdep\n\n')
output.write('prefix := /usr/local\nFC := f95\nFFLAGS := -O2\nLDLIBS :=\n\n')
output.write('programs := ' + ' '.join(sorted([x.objfile.fnexe for x in allprograms])) + '\n')
if make_vpath:
output.write('VPATH := {}\n'.format(':'.join(make_vpath)))
output.write('\nall: $(programs)\n\n')
#--------------------------------------------------------------------------#
# model is complete, now we can generate the products
for u in sorted(universe, key = lambda u: u.objfile.fnobj if u.objfile else ''):
if u.objfile == None:
stderr.write('warning: module {} was not found in any file\n'.format(u))
continue
deps_fixed = set(x.objfile.fnobj for x in u.deps if x.objfile != None)
if (not args.no_includes) or args.whole:
deps_fixed |= u.includes
deps_fixed -= set((u.objfile.fnobj,))
if len(deps_fixed) == 0: continue
output.write('{}: {}\n'.format(u.objfile.fnobj, ' '.join(sorted(deps_fixed))))
#--------------------------------------------------------------------------#
if args.whole or args.programs:
output.write('\n')
for p in sorted(allprograms, key = lambda u: u.objfile.fnobj):
d = set(x.objfile.fnobj for x in walktree(p) if x.objfile != None)
output.write('{}: {}\n'.format(p.objfile.fnexe, ' '.join(sorted(d))))
#--------------------------------------------------------------------------#
if args.whole:
output.write('\n')
output.write('%.o: %.f90\n\t$(FC) $(INCLUDE) $(FFLAGS) -c $< -o $@\n')
output.write('%.o: %.F90\n\t$(FC) $(INCLUDE) $(CPPFLAGS) $(FFLAGS) -c $< -o $@\n')
output.write('$(programs):\n\t$(FC) $(INCLUDE) $(FFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@\n')
output.write('\ninstall:\n\tinstall -d $(prefix)/bin\n'
'\tinstall $(programs) $(prefix)/bin\n\n')
output.write('clean:\n\t$(RM) *.o *.mod *.smod $(programs)\n\n')
output.write('.PHONY: all install clean\n')
#--------------------------------------------------------------------------#
if verbose:
stderr.write('\n')
for u in universe:
stderr.write(u.summ() + '\n')
#------------------------------------------------------------------------------#
if __name__ == '__main__':
main()