diff options
Diffstat (limited to 'Tools/fc.py')
-rw-r--r-- | Tools/fc.py | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/Tools/fc.py b/Tools/fc.py new file mode 100644 index 0000000..d9e8d8c --- /dev/null +++ b/Tools/fc.py @@ -0,0 +1,189 @@ +#! /usr/bin/env python +# encoding: utf-8 +# DC 2008 +# Thomas Nagy 2016-2018 (ita) + +""" +Fortran support +""" + +from waflib import Utils, Task, Errors +from waflib.Tools import ccroot, fc_config, fc_scan +from waflib.TaskGen import extension +from waflib.Configure import conf + +ccroot.USELIB_VARS['fc'] = set(['FCFLAGS', 'DEFINES', 'INCLUDES', 'FCPPFLAGS']) +ccroot.USELIB_VARS['fcprogram_test'] = ccroot.USELIB_VARS['fcprogram'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS']) +ccroot.USELIB_VARS['fcshlib'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS']) +ccroot.USELIB_VARS['fcstlib'] = set(['ARFLAGS', 'LINKDEPS']) + +@extension('.f','.F','.f90','.F90','.for','.FOR','.f95','.F95','.f03','.F03','.f08','.F08') +def fc_hook(self, node): + "Binds the Fortran file extensions create :py:class:`waflib.Tools.fc.fc` instances" + return self.create_compiled_task('fc', node) + +@conf +def modfile(conf, name): + """ + Turns a module name into the right module file name. + Defaults to all lower case. + """ + return {'lower' :name.lower() + '.mod', + 'lower.MOD' :name.lower() + '.MOD', + 'UPPER.mod' :name.upper() + '.mod', + 'UPPER' :name.upper() + '.MOD'}[conf.env.FC_MOD_CAPITALIZATION or 'lower'] + +def get_fortran_tasks(tsk): + """ + Obtains all fortran tasks from the same build group. Those tasks must not have + the attribute 'nomod' or 'mod_fortran_done' + + :return: a list of :py:class:`waflib.Tools.fc.fc` instances + """ + bld = tsk.generator.bld + tasks = bld.get_tasks_group(bld.get_group_idx(tsk.generator)) + return [x for x in tasks if isinstance(x, fc) and not getattr(x, 'nomod', None) and not getattr(x, 'mod_fortran_done', None)] + +class fc(Task.Task): + """ + Fortran tasks can only run when all fortran tasks in a current task group are ready to be executed + This may cause a deadlock if some fortran task is waiting for something that cannot happen (circular dependency) + Should this ever happen, set the 'nomod=True' on those tasks instances to break the loop + """ + color = 'GREEN' + run_str = '${FC} ${FCFLAGS} ${FCINCPATH_ST:INCPATHS} ${FCDEFINES_ST:DEFINES} ${_FCMODOUTFLAGS} ${FC_TGT_F}${TGT[0].abspath()} ${FC_SRC_F}${SRC[0].abspath()} ${FCPPFLAGS}' + vars = ["FORTRANMODPATHFLAG"] + + def scan(self): + """Fortran dependency scanner""" + tmp = fc_scan.fortran_parser(self.generator.includes_nodes) + tmp.task = self + tmp.start(self.inputs[0]) + return (tmp.nodes, tmp.names) + + def runnable_status(self): + """ + Sets the mod file outputs and the dependencies on the mod files over all Fortran tasks + executed by the main thread so there are no concurrency issues + """ + if getattr(self, 'mod_fortran_done', None): + return super(fc, self).runnable_status() + + # now, if we reach this part it is because this fortran task is the first in the list + bld = self.generator.bld + + # obtain the fortran tasks + lst = get_fortran_tasks(self) + + # disable this method for other tasks + for tsk in lst: + tsk.mod_fortran_done = True + + # wait for all the .f tasks to be ready for execution + # and ensure that the scanners are called at least once + for tsk in lst: + ret = tsk.runnable_status() + if ret == Task.ASK_LATER: + # we have to wait for one of the other fortran tasks to be ready + # this may deadlock if there are dependencies between fortran tasks + # but this should not happen (we are setting them here!) + for x in lst: + x.mod_fortran_done = None + + return Task.ASK_LATER + + ins = Utils.defaultdict(set) + outs = Utils.defaultdict(set) + + # the .mod files to create + for tsk in lst: + key = tsk.uid() + for x in bld.raw_deps[key]: + if x.startswith('MOD@'): + name = bld.modfile(x.replace('MOD@', '')) + node = bld.srcnode.find_or_declare(name) + tsk.set_outputs(node) + outs[node].add(tsk) + + # the .mod files to use + for tsk in lst: + key = tsk.uid() + for x in bld.raw_deps[key]: + if x.startswith('USE@'): + name = bld.modfile(x.replace('USE@', '')) + node = bld.srcnode.find_resource(name) + if node and node not in tsk.outputs: + if not node in bld.node_deps[key]: + bld.node_deps[key].append(node) + ins[node].add(tsk) + + # if the intersection matches, set the order + for k in ins.keys(): + for a in ins[k]: + a.run_after.update(outs[k]) + for x in outs[k]: + self.generator.bld.producer.revdeps[x].add(a) + + # the scanner cannot output nodes, so we have to set them + # ourselves as task.dep_nodes (additional input nodes) + tmp = [] + for t in outs[k]: + tmp.extend(t.outputs) + a.dep_nodes.extend(tmp) + a.dep_nodes.sort(key=lambda x: x.abspath()) + + # the task objects have changed: clear the signature cache + for tsk in lst: + try: + delattr(tsk, 'cache_sig') + except AttributeError: + pass + + return super(fc, self).runnable_status() + +class fcprogram(ccroot.link_task): + """Links Fortran programs""" + color = 'YELLOW' + 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}' + inst_to = '${BINDIR}' + +class fcshlib(fcprogram): + """Links Fortran libraries""" + inst_to = '${LIBDIR}' + +class fcstlib(ccroot.stlink_task): + """Links Fortran static libraries (uses ar by default)""" + pass # do not remove the pass statement + +class fcprogram_test(fcprogram): + """Custom link task to obtain compiler outputs for Fortran configuration tests""" + + def runnable_status(self): + """This task is always executed""" + ret = super(fcprogram_test, self).runnable_status() + if ret == Task.SKIP_ME: + ret = Task.RUN_ME + return ret + + def exec_command(self, cmd, **kw): + """Stores the compiler std our/err onto the build context, to bld.out + bld.err""" + bld = self.generator.bld + + kw['shell'] = isinstance(cmd, str) + kw['stdout'] = kw['stderr'] = Utils.subprocess.PIPE + kw['cwd'] = self.get_cwd() + bld.out = bld.err = '' + + bld.to_log('command: %s\n' % cmd) + + kw['output'] = 0 + try: + (bld.out, bld.err) = bld.cmd_and_log(cmd, **kw) + except Errors.WafError: + return -1 + + if bld.out: + bld.to_log('out: %s\n' % bld.out) + if bld.err: + bld.to_log('err: %s\n' % bld.err) + |