swig.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. #! /usr/bin/env python
  2. # encoding: UTF-8
  3. # Petar Forai
  4. # Thomas Nagy 2008-2010 (ita)
  5. import re
  6. from waflib import Task, Logs
  7. from waflib.TaskGen import extension, feature, after_method
  8. from waflib.Configure import conf
  9. from waflib.Tools import c_preproc
  10. """
  11. tasks have to be added dynamically:
  12. - swig interface files may be created at runtime
  13. - the module name may be unknown in advance
  14. """
  15. SWIG_EXTS = ['.swig', '.i']
  16. re_module = re.compile('%module(?:\s*\(.*\))?\s+(.+)', re.M)
  17. re_1 = re.compile(r'^%module.*?\s+([\w]+)\s*?$', re.M)
  18. re_2 = re.compile('[#%]include [<"](.*)[">]', re.M)
  19. class swig(Task.Task):
  20. color = 'BLUE'
  21. run_str = '${SWIG} ${SWIGFLAGS} ${SWIGPATH_ST:INCPATHS} ${SWIGDEF_ST:DEFINES} ${SRC}'
  22. ext_out = ['.h'] # might produce .h files although it is not mandatory
  23. vars = ['SWIG_VERSION', 'SWIGDEPS']
  24. def runnable_status(self):
  25. for t in self.run_after:
  26. if not t.hasrun:
  27. return Task.ASK_LATER
  28. if not getattr(self, 'init_outputs', None):
  29. self.init_outputs = True
  30. if not getattr(self, 'module', None):
  31. # search the module name
  32. txt = self.inputs[0].read()
  33. m = re_module.search(txt)
  34. if not m:
  35. raise ValueError("could not find the swig module name")
  36. self.module = m.group(1)
  37. swig_c(self)
  38. # add the language-specific output files as nodes
  39. # call funs in the dict swig_langs
  40. for x in self.env['SWIGFLAGS']:
  41. # obtain the language
  42. x = x[1:]
  43. try:
  44. fun = swig_langs[x]
  45. except KeyError:
  46. pass
  47. else:
  48. fun(self)
  49. return super(swig, self).runnable_status()
  50. def scan(self):
  51. "scan for swig dependencies, climb the .i files"
  52. lst_src = []
  53. seen = []
  54. missing = []
  55. to_see = [self.inputs[0]]
  56. while to_see:
  57. node = to_see.pop(0)
  58. if node in seen:
  59. continue
  60. seen.append(node)
  61. lst_src.append(node)
  62. # read the file
  63. code = node.read()
  64. code = c_preproc.re_nl.sub('', code)
  65. code = c_preproc.re_cpp.sub(c_preproc.repl, code)
  66. # find .i files and project headers
  67. names = re_2.findall(code)
  68. for n in names:
  69. for d in self.generator.includes_nodes + [node.parent]:
  70. u = d.find_resource(n)
  71. if u:
  72. to_see.append(u)
  73. break
  74. else:
  75. missing.append(n)
  76. return (lst_src, missing)
  77. # provide additional language processing
  78. swig_langs = {}
  79. def swigf(fun):
  80. swig_langs[fun.__name__.replace('swig_', '')] = fun
  81. return fun
  82. swig.swigf = swigf
  83. def swig_c(self):
  84. ext = '.swigwrap_%d.c' % self.generator.idx
  85. flags = self.env['SWIGFLAGS']
  86. if '-c++' in flags:
  87. ext += 'xx'
  88. out_node = self.inputs[0].parent.find_or_declare(self.module + ext)
  89. if '-c++' in flags:
  90. c_tsk = self.generator.cxx_hook(out_node)
  91. else:
  92. c_tsk = self.generator.c_hook(out_node)
  93. c_tsk.set_run_after(self)
  94. # transfer weights from swig task to c task
  95. if getattr(self, 'weight', None):
  96. c_tsk.weight = self.weight
  97. if getattr(self, 'tree_weight', None):
  98. c_tsk.tree_weight = self.tree_weight
  99. try:
  100. self.more_tasks.append(c_tsk)
  101. except AttributeError:
  102. self.more_tasks = [c_tsk]
  103. try:
  104. ltask = self.generator.link_task
  105. except AttributeError:
  106. pass
  107. else:
  108. ltask.set_run_after(c_tsk)
  109. # setting input nodes does not declare the build order
  110. # because the build already started, but it sets
  111. # the dependency to enable rebuilds
  112. ltask.inputs.append(c_tsk.outputs[0])
  113. self.outputs.append(out_node)
  114. if not '-o' in self.env['SWIGFLAGS']:
  115. self.env.append_value('SWIGFLAGS', ['-o', self.outputs[0].abspath()])
  116. @swigf
  117. def swig_python(tsk):
  118. node = tsk.inputs[0].parent
  119. if tsk.outdir:
  120. node = tsk.outdir
  121. tsk.set_outputs(node.find_or_declare(tsk.module+'.py'))
  122. @swigf
  123. def swig_ocaml(tsk):
  124. node = tsk.inputs[0].parent
  125. if tsk.outdir:
  126. node = tsk.outdir
  127. tsk.set_outputs(node.find_or_declare(tsk.module+'.ml'))
  128. tsk.set_outputs(node.find_or_declare(tsk.module+'.mli'))
  129. @extension(*SWIG_EXTS)
  130. def i_file(self, node):
  131. # the task instance
  132. tsk = self.create_task('swig')
  133. tsk.set_inputs(node)
  134. tsk.module = getattr(self, 'swig_module', None)
  135. flags = self.to_list(getattr(self, 'swig_flags', []))
  136. tsk.env.append_value('SWIGFLAGS', flags)
  137. tsk.outdir = None
  138. if '-outdir' in flags:
  139. outdir = flags[flags.index('-outdir')+1]
  140. outdir = tsk.generator.bld.bldnode.make_node(outdir)
  141. outdir.mkdir()
  142. tsk.outdir = outdir
  143. @feature('c', 'cxx', 'd', 'fc', 'asm')
  144. @after_method('apply_link', 'process_source')
  145. def enforce_swig_before_link(self):
  146. try:
  147. link_task = self.link_task
  148. except AttributeError:
  149. pass
  150. else:
  151. for x in self.tasks:
  152. if x.__class__.__name__ == 'swig':
  153. link_task.run_after.add(x)
  154. @conf
  155. def check_swig_version(conf, minver=None):
  156. """
  157. Check if the swig tool is found matching a given minimum version.
  158. minver should be a tuple, eg. to check for swig >= 1.3.28 pass (1,3,28) as minver.
  159. If successful, SWIG_VERSION is defined as 'MAJOR.MINOR'
  160. (eg. '1.3') of the actual swig version found.
  161. :param minver: minimum version
  162. :type minver: tuple of int
  163. :return: swig version
  164. :rtype: tuple of int
  165. """
  166. assert minver is None or isinstance(minver, tuple)
  167. swigbin = conf.env['SWIG']
  168. if not swigbin:
  169. conf.fatal('could not find the swig executable')
  170. # Get swig version string
  171. cmd = swigbin + ['-version']
  172. Logs.debug('swig: Running swig command %r', cmd)
  173. reg_swig = re.compile(r'SWIG Version\s(.*)', re.M)
  174. swig_out = conf.cmd_and_log(cmd)
  175. swigver_tuple = tuple([int(s) for s in reg_swig.findall(swig_out)[0].split('.')])
  176. # Compare swig version with the minimum required
  177. result = (minver is None) or (swigver_tuple >= minver)
  178. if result:
  179. # Define useful environment variables
  180. swigver = '.'.join([str(x) for x in swigver_tuple[:2]])
  181. conf.env['SWIG_VERSION'] = swigver
  182. # Feedback
  183. swigver_full = '.'.join(map(str, swigver_tuple[:3]))
  184. if minver is None:
  185. conf.msg('Checking for swig version', swigver_full)
  186. else:
  187. minver_str = '.'.join(map(str, minver))
  188. conf.msg('Checking for swig version >= %s' % (minver_str,), swigver_full, color=result and 'GREEN' or 'YELLOW')
  189. if not result:
  190. conf.fatal('The swig version is too old, expecting %r' % (minver,))
  191. return swigver_tuple
  192. def configure(conf):
  193. conf.find_program('swig', var='SWIG')
  194. conf.env.SWIGPATH_ST = '-I%s'
  195. conf.env.SWIGDEF_ST = '-D%s'