fc.py 6.6 KB

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