jack2 codebase
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

150 lines
4.1KB

  1. #! /usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2010-2015
  4. import os,re
  5. import waflib
  6. import waflib.Logs as _msg
  7. from waflib import Task, Logs
  8. from waflib.TaskGen import extension, feature, before_method, after_method
  9. cy_api_pat = re.compile(r'\s*?cdef\s*?(public|api)\w*')
  10. re_cyt = re.compile(r"""
  11. (?:from\s+(\w+)\s+)? # optionally match "from foo" and capture foo
  12. c?import\s(\w+|[*]) # require "import bar" and capture bar
  13. """, re.M | re.VERBOSE)
  14. @extension('.pyx')
  15. def add_cython_file(self, node):
  16. """
  17. Process a *.pyx* file given in the list of source files. No additional
  18. feature is required::
  19. def build(bld):
  20. bld(features='c cshlib pyext', source='main.c foo.pyx', target='app')
  21. """
  22. ext = '.c'
  23. if 'cxx' in self.features:
  24. self.env.append_unique('CYTHONFLAGS', '--cplus')
  25. ext = '.cc'
  26. for x in getattr(self, 'cython_includes', []):
  27. # TODO re-use these nodes in "scan" below
  28. d = self.path.find_dir(x)
  29. if d:
  30. self.env.append_unique('CYTHONFLAGS', '-I%s' % d.abspath())
  31. tsk = self.create_task('cython', node, node.change_ext(ext))
  32. self.source += tsk.outputs
  33. class cython(Task.Task):
  34. run_str = '${CYTHON} ${CYTHONFLAGS} -o ${TGT[0].abspath()} ${SRC}'
  35. color = 'GREEN'
  36. vars = ['INCLUDES']
  37. """
  38. Rebuild whenever the INCLUDES change. The variables such as CYTHONFLAGS will be appended
  39. by the metaclass.
  40. """
  41. ext_out = ['.h']
  42. """
  43. The creation of a .h file is known only after the build has begun, so it is not
  44. possible to compute a build order just by looking at the task inputs/outputs.
  45. """
  46. def runnable_status(self):
  47. """
  48. Perform a double-check to add the headers created by cython
  49. to the output nodes. The scanner is executed only when the cython task
  50. must be executed (optimization).
  51. """
  52. ret = super(cython, self).runnable_status()
  53. if ret == Task.ASK_LATER:
  54. return ret
  55. for x in self.generator.bld.raw_deps[self.uid()]:
  56. if x.startswith('header:'):
  57. self.outputs.append(self.inputs[0].parent.find_or_declare(x.replace('header:', '')))
  58. return super(cython, self).runnable_status()
  59. def post_run(self):
  60. for x in self.outputs:
  61. if x.name.endswith('.h'):
  62. if not os.path.exists(x.abspath()):
  63. if Logs.verbose:
  64. Logs.warn('Expected %r' % x.abspath())
  65. x.write('')
  66. return Task.Task.post_run(self)
  67. def scan(self):
  68. """
  69. Return the dependent files (.pxd) by looking in the include folders.
  70. Put the headers to generate in the custom list "bld.raw_deps".
  71. To inspect the scanne results use::
  72. $ waf clean build --zones=deps
  73. """
  74. node = self.inputs[0]
  75. txt = node.read()
  76. mods = []
  77. for m in re_cyt.finditer(txt):
  78. if m.group(1): # matches "from foo import bar"
  79. mods.append(m.group(1))
  80. else:
  81. mods.append(m.group(2))
  82. _msg.debug("cython: mods %r" % mods)
  83. incs = getattr(self.generator, 'cython_includes', [])
  84. incs = [self.generator.path.find_dir(x) for x in incs]
  85. incs.append(node.parent)
  86. found = []
  87. missing = []
  88. for x in mods:
  89. for y in incs:
  90. k = y.find_resource(x + '.pxd')
  91. if k:
  92. found.append(k)
  93. break
  94. else:
  95. missing.append(x)
  96. # the cython file implicitly depends on a pxd file that might be present
  97. implicit = node.parent.find_resource(node.name[:-3] + 'pxd')
  98. if implicit:
  99. found.append(implicit)
  100. _msg.debug("cython: found %r" % found)
  101. # Now the .h created - store them in bld.raw_deps for later use
  102. has_api = False
  103. has_public = False
  104. for l in txt.splitlines():
  105. if cy_api_pat.match(l):
  106. if ' api ' in l:
  107. has_api = True
  108. if ' public ' in l:
  109. has_public = True
  110. name = node.name.replace('.pyx', '')
  111. if has_api:
  112. missing.append('header:%s_api.h' % name)
  113. if has_public:
  114. missing.append('header:%s.h' % name)
  115. return (found, missing)
  116. def options(ctx):
  117. ctx.add_option('--cython-flags', action='store', default='', help='space separated list of flags to pass to cython')
  118. def configure(ctx):
  119. if not ctx.env.CC and not ctx.env.CXX:
  120. ctx.fatal('Load a C/C++ compiler first')
  121. if not ctx.env.PYTHON:
  122. ctx.fatal('Load the python tool first!')
  123. ctx.find_program('cython', var='CYTHON')
  124. if ctx.options.cython_flags:
  125. ctx.env.CYTHONFLAGS = ctx.options.cython_flags