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