waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

cython.py (4197B)


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