1#! /usr/bin/env python
2# encoding: utf-8
3# DC 2008
4# Thomas Nagy 2016-2018 (ita)
5
6"""
7Fortran support
8"""
9
10from waflib import Utils, Task, Errors
11from waflib.Tools import ccroot, fc_config, fc_scan
12from waflib.TaskGen import extension
13from waflib.Configure import conf
14
15ccroot.USELIB_VARS['fc'] = set(['FCFLAGS', 'DEFINES', 'INCLUDES', 'FCPPFLAGS'])
16ccroot.USELIB_VARS['fcprogram_test'] = ccroot.USELIB_VARS['fcprogram'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS'])
17ccroot.USELIB_VARS['fcshlib'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS'])
18ccroot.USELIB_VARS['fcstlib'] = set(['ARFLAGS', 'LINKDEPS'])
19
20@extension('.f','.F','.f90','.F90','.for','.FOR','.f95','.F95','.f03','.F03','.f08','.F08')
21def fc_hook(self, node):
22	"Binds the Fortran file extensions create :py:class:`waflib.Tools.fc.fc` instances"
23	return self.create_compiled_task('fc', node)
24
25@conf
26def modfile(conf, name):
27	"""
28	Turns a module name into the right module file name.
29	Defaults to all lower case.
30	"""
31	if name.find(':') >= 0:
32		# Depending on a submodule!
33		separator = conf.env.FC_SUBMOD_SEPARATOR or '@'
34		# Ancestors of the submodule will be prefixed to the
35		# submodule name, separated by a colon.
36		modpath = name.split(':')
37		# Only the ancestor (actual) module and the submodule name
38		# will be used for the filename.
39		modname = modpath[0] + separator + modpath[-1]
40		suffix = conf.env.FC_SUBMOD_SUFFIX or '.smod'
41	else:
42		modname = name
43		suffix = '.mod'
44
45	return {'lower'     :modname.lower() + suffix.lower(),
46		'lower.MOD' :modname.lower() + suffix.upper(),
47		'UPPER.mod' :modname.upper() + suffix.lower(),
48		'UPPER'     :modname.upper() + suffix.upper()}[conf.env.FC_MOD_CAPITALIZATION or 'lower']
49
50def get_fortran_tasks(tsk):
51	"""
52	Obtains all fortran tasks from the same build group. Those tasks must not have
53	the attribute 'nomod' or 'mod_fortran_done'
54
55	:return: a list of :py:class:`waflib.Tools.fc.fc` instances
56	"""
57	bld = tsk.generator.bld
58	tasks = bld.get_tasks_group(bld.get_group_idx(tsk.generator))
59	return [x for x in tasks if isinstance(x, fc) and not getattr(x, 'nomod', None) and not getattr(x, 'mod_fortran_done', None)]
60
61class fc(Task.Task):
62	"""
63	Fortran tasks can only run when all fortran tasks in a current task group are ready to be executed
64	This may cause a deadlock if some fortran task is waiting for something that cannot happen (circular dependency)
65	Should this ever happen, set the 'nomod=True' on those tasks instances to break the loop
66	"""
67	color = 'GREEN'
68	run_str = '${FC} ${FCFLAGS} ${FCINCPATH_ST:INCPATHS} ${FCDEFINES_ST:DEFINES} ${_FCMODOUTFLAGS} ${FC_TGT_F}${TGT[0].abspath()} ${FC_SRC_F}${SRC[0].abspath()} ${FCPPFLAGS}'
69	vars = ["FORTRANMODPATHFLAG"]
70
71	def scan(self):
72		"""Fortran dependency scanner"""
73		tmp = fc_scan.fortran_parser(self.generator.includes_nodes)
74		tmp.task = self
75		tmp.start(self.inputs[0])
76		return (tmp.nodes, tmp.names)
77
78	def runnable_status(self):
79		"""
80		Sets the mod file outputs and the dependencies on the mod files over all Fortran tasks
81		executed by the main thread so there are no concurrency issues
82		"""
83		if getattr(self, 'mod_fortran_done', None):
84			return super(fc, self).runnable_status()
85
86		# now, if we reach this part it is because this fortran task is the first in the list
87		bld = self.generator.bld
88
89		# obtain the fortran tasks
90		lst = get_fortran_tasks(self)
91
92		# disable this method for other tasks
93		for tsk in lst:
94			tsk.mod_fortran_done = True
95
96		# wait for all the .f tasks to be ready for execution
97		# and ensure that the scanners are called at least once
98		for tsk in lst:
99			ret = tsk.runnable_status()
100			if ret == Task.ASK_LATER:
101				# we have to wait for one of the other fortran tasks to be ready
102				# this may deadlock if there are dependencies between fortran tasks
103				# but this should not happen (we are setting them here!)
104				for x in lst:
105					x.mod_fortran_done = None
106
107				return Task.ASK_LATER
108
109		ins = Utils.defaultdict(set)
110		outs = Utils.defaultdict(set)
111
112		# the .mod files to create
113		for tsk in lst:
114			key = tsk.uid()
115			for x in bld.raw_deps[key]:
116				if x.startswith('MOD@'):
117					name = bld.modfile(x.replace('MOD@', ''))
118					node = bld.srcnode.find_or_declare(name)
119					tsk.set_outputs(node)
120					outs[node].add(tsk)
121
122		# the .mod files to use
123		for tsk in lst:
124			key = tsk.uid()
125			for x in bld.raw_deps[key]:
126				if x.startswith('USE@'):
127					name = bld.modfile(x.replace('USE@', ''))
128					node = bld.srcnode.find_resource(name)
129					if node and node not in tsk.outputs:
130						if not node in bld.node_deps[key]:
131							bld.node_deps[key].append(node)
132						ins[node].add(tsk)
133
134		# if the intersection matches, set the order
135		for k in ins.keys():
136			for a in ins[k]:
137				a.run_after.update(outs[k])
138				for x in outs[k]:
139					self.generator.bld.producer.revdeps[x].add(a)
140
141				# the scanner cannot output nodes, so we have to set them
142				# ourselves as task.dep_nodes (additional input nodes)
143				tmp = []
144				for t in outs[k]:
145					tmp.extend(t.outputs)
146				a.dep_nodes.extend(tmp)
147				a.dep_nodes.sort(key=lambda x: x.abspath())
148
149		# the task objects have changed: clear the signature cache
150		for tsk in lst:
151			try:
152				delattr(tsk, 'cache_sig')
153			except AttributeError:
154				pass
155
156		return super(fc, self).runnable_status()
157
158class fcprogram(ccroot.link_task):
159	"""Links Fortran programs"""
160	color = 'YELLOW'
161	run_str = '${FC} ${LINKFLAGS} ${FCLNK_SRC_F}${SRC} ${FCLNK_TGT_F}${TGT[0].abspath()} ${RPATH_ST:RPATH} ${FCSTLIB_MARKER} ${FCSTLIBPATH_ST:STLIBPATH} ${FCSTLIB_ST:STLIB} ${FCSHLIB_MARKER} ${FCLIBPATH_ST:LIBPATH} ${FCLIB_ST:LIB} ${LDFLAGS}'
162	inst_to = '${BINDIR}'
163
164class fcshlib(fcprogram):
165	"""Links Fortran libraries"""
166	inst_to = '${LIBDIR}'
167
168class fcstlib(ccroot.stlink_task):
169	"""Links Fortran static libraries (uses ar by default)"""
170	pass # do not remove the pass statement
171
172class fcprogram_test(fcprogram):
173	"""Custom link task to obtain compiler outputs for Fortran configuration tests"""
174
175	def runnable_status(self):
176		"""This task is always executed"""
177		ret = super(fcprogram_test, self).runnable_status()
178		if ret == Task.SKIP_ME:
179			ret = Task.RUN_ME
180		return ret
181
182	def exec_command(self, cmd, **kw):
183		"""Stores the compiler std our/err onto the build context, to bld.out + bld.err"""
184		bld = self.generator.bld
185
186		kw['shell'] = isinstance(cmd, str)
187		kw['stdout'] = kw['stderr'] = Utils.subprocess.PIPE
188		kw['cwd'] = self.get_cwd()
189		bld.out = bld.err = ''
190
191		bld.to_log('command: %s\n' % cmd)
192
193		kw['output'] = 0
194		try:
195			(bld.out, bld.err) = bld.cmd_and_log(cmd, **kw)
196		except Errors.WafError:
197			return -1
198
199		if bld.out:
200			bld.to_log('out: %s\n' % bld.out)
201		if bld.err:
202			bld.to_log('err: %s\n' % bld.err)
203
204