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