1"""Cython.Distutils.old_build_ext
2
3Implements a version of the Distutils 'build_ext' command, for
4building Cython extension modules.
5
6Note that this module is deprecated.  Use cythonize() instead.
7"""
8
9__revision__ = "$Id:$"
10
11import sys
12import os
13from distutils.errors import DistutilsPlatformError
14from distutils.dep_util import newer, newer_group
15from distutils import log
16from distutils.command import build_ext as _build_ext
17from distutils import sysconfig
18
19
20try:
21    from __builtin__ import basestring
22except ImportError:
23    basestring = str
24
25
26# FIXME: the below does not work as intended since importing 'Cython.Distutils' already
27#        imports this module through 'Cython/Distutils/build_ext.py', so the condition is
28#        always false and never prints the warning.
29"""
30import inspect
31import warnings
32
33def _check_stack(path):
34    try:
35        for frame in inspect.getouterframes(inspect.currentframe(), 0):
36            if path in frame[1].replace(os.sep, '/'):
37                return True
38    except Exception:
39        pass
40    return False
41
42
43if (not _check_stack('setuptools/extensions.py')
44        and not _check_stack('pyximport/pyxbuild.py')
45        and not _check_stack('Cython/Distutils/build_ext.py')):
46    warnings.warn(
47        "Cython.Distutils.old_build_ext does not properly handle dependencies "
48        "and is deprecated.")
49"""
50
51extension_name_re = _build_ext.extension_name_re
52
53show_compilers = _build_ext.show_compilers
54
55class Optimization(object):
56    def __init__(self):
57        self.flags = (
58            'OPT',
59            'CFLAGS',
60            'CPPFLAGS',
61            'EXTRA_CFLAGS',
62            'BASECFLAGS',
63            'PY_CFLAGS',
64        )
65        self.state = sysconfig.get_config_vars(*self.flags)
66        self.config_vars = sysconfig.get_config_vars()
67
68
69    def disable_optimization(self):
70        "disable optimization for the C or C++ compiler"
71        badoptions = ('-O1', '-O2', '-O3')
72
73        for flag, option in zip(self.flags, self.state):
74            if option is not None:
75                L = [opt for opt in option.split() if opt not in badoptions]
76                self.config_vars[flag] = ' '.join(L)
77
78    def restore_state(self):
79        "restore the original state"
80        for flag, option in zip(self.flags, self.state):
81            if option is not None:
82                self.config_vars[flag] = option
83
84
85optimization = Optimization()
86
87
88class old_build_ext(_build_ext.build_ext):
89
90    description = "build C/C++ and Cython extensions (compile/link to build directory)"
91
92    sep_by = _build_ext.build_ext.sep_by
93    user_options = _build_ext.build_ext.user_options[:]
94    boolean_options = _build_ext.build_ext.boolean_options[:]
95    help_options = _build_ext.build_ext.help_options[:]
96
97    # Add the pyrex specific data.
98    user_options.extend([
99        ('cython-cplus', None,
100         "generate C++ source files"),
101        ('cython-create-listing', None,
102         "write errors to a listing file"),
103        ('cython-line-directives', None,
104         "emit source line directives"),
105        ('cython-include-dirs=', None,
106         "path to the Cython include files" + sep_by),
107        ('cython-c-in-temp', None,
108         "put generated C files in temp directory"),
109        ('cython-gen-pxi', None,
110            "generate .pxi file for public declarations"),
111        ('cython-directives=', None,
112            "compiler directive overrides"),
113        ('cython-gdb', None,
114         "generate debug information for cygdb"),
115        ('cython-compile-time-env', None,
116            "cython compile time environment"),
117
118        # For backwards compatibility.
119        ('pyrex-cplus', None,
120         "generate C++ source files"),
121        ('pyrex-create-listing', None,
122         "write errors to a listing file"),
123        ('pyrex-line-directives', None,
124         "emit source line directives"),
125        ('pyrex-include-dirs=', None,
126         "path to the Cython include files" + sep_by),
127        ('pyrex-c-in-temp', None,
128         "put generated C files in temp directory"),
129        ('pyrex-gen-pxi', None,
130            "generate .pxi file for public declarations"),
131        ('pyrex-directives=', None,
132            "compiler directive overrides"),
133        ('pyrex-gdb', None,
134         "generate debug information for cygdb"),
135        ])
136
137    boolean_options.extend([
138        'cython-cplus', 'cython-create-listing', 'cython-line-directives',
139        'cython-c-in-temp', 'cython-gdb',
140
141        # For backwards compatibility.
142        'pyrex-cplus', 'pyrex-create-listing', 'pyrex-line-directives',
143        'pyrex-c-in-temp', 'pyrex-gdb',
144    ])
145
146    def initialize_options(self):
147        _build_ext.build_ext.initialize_options(self)
148        self.cython_cplus = 0
149        self.cython_create_listing = 0
150        self.cython_line_directives = 0
151        self.cython_include_dirs = None
152        self.cython_directives = None
153        self.cython_c_in_temp = 0
154        self.cython_gen_pxi = 0
155        self.cython_gdb = False
156        self.no_c_in_traceback = 0
157        self.cython_compile_time_env = None
158
159    def __getattr__(self, name):
160        if name[:6] == 'pyrex_':
161            return getattr(self, 'cython_' + name[6:])
162        else:
163            return _build_ext.build_ext.__getattr__(self, name)
164
165    def __setattr__(self, name, value):
166        if name[:6] == 'pyrex_':
167            return setattr(self, 'cython_' + name[6:], value)
168        else:
169            # _build_ext.build_ext.__setattr__(self, name, value)
170            self.__dict__[name] = value
171
172    def finalize_options(self):
173        _build_ext.build_ext.finalize_options(self)
174        if self.cython_include_dirs is None:
175            self.cython_include_dirs = []
176        elif isinstance(self.cython_include_dirs, basestring):
177            self.cython_include_dirs = \
178                self.cython_include_dirs.split(os.pathsep)
179        if self.cython_directives is None:
180            self.cython_directives = {}
181    # finalize_options ()
182
183    def run(self):
184        # We have one shot at this before build_ext initializes the compiler.
185        # If --pyrex-gdb is in effect as a command line option or as option
186        # of any Extension module, disable optimization for the C or C++
187        # compiler.
188        if self.cython_gdb or [1 for ext in self.extensions
189                                     if getattr(ext, 'cython_gdb', False)]:
190            optimization.disable_optimization()
191
192        _build_ext.build_ext.run(self)
193
194    def check_extensions_list(self, extensions):
195        # Note: might get called multiple times.
196        _build_ext.build_ext.check_extensions_list(self, extensions)
197        for ext in self.extensions:
198            ext.sources = self.cython_sources(ext.sources, ext)
199
200    def cython_sources(self, sources, extension):
201        """
202        Walk the list of source files in 'sources', looking for Cython
203        source files (.pyx and .py).  Run Cython on all that are
204        found, and return a modified 'sources' list with Cython source
205        files replaced by the generated C (or C++) files.
206        """
207        new_sources = []
208        cython_sources = []
209        cython_targets = {}
210
211        # Setup create_list and cplus from the extension options if
212        # Cython.Distutils.extension.Extension is used, otherwise just
213        # use what was parsed from the command-line or the configuration file.
214        # cplus will also be set to true is extension.language is equal to
215        # 'C++' or 'c++'.
216        #try:
217        #    create_listing = self.cython_create_listing or \
218        #                        extension.cython_create_listing
219        #    cplus = self.cython_cplus or \
220        #                extension.cython_cplus or \
221        #                (extension.language != None and \
222        #                    extension.language.lower() == 'c++')
223        #except AttributeError:
224        #    create_listing = self.cython_create_listing
225        #    cplus = self.cython_cplus or \
226        #                (extension.language != None and \
227        #                    extension.language.lower() == 'c++')
228
229        create_listing = self.cython_create_listing or \
230            getattr(extension, 'cython_create_listing', 0)
231        line_directives = self.cython_line_directives or \
232            getattr(extension, 'cython_line_directives', 0)
233        no_c_in_traceback = self.no_c_in_traceback or \
234            getattr(extension, 'no_c_in_traceback', 0)
235        cplus = self.cython_cplus or getattr(extension, 'cython_cplus', 0) or \
236                (extension.language and extension.language.lower() == 'c++')
237        cython_gen_pxi = self.cython_gen_pxi or getattr(extension, 'cython_gen_pxi', 0)
238        cython_gdb = self.cython_gdb or getattr(extension, 'cython_gdb', False)
239        cython_compile_time_env = self.cython_compile_time_env or \
240            getattr(extension, 'cython_compile_time_env', None)
241
242        # Set up the include_path for the Cython compiler:
243        #    1.    Start with the command line option.
244        #    2.    Add in any (unique) paths from the extension
245        #        cython_include_dirs (if Cython.Distutils.extension is used).
246        #    3.    Add in any (unique) paths from the extension include_dirs
247        includes = list(self.cython_include_dirs)
248        try:
249            for i in extension.cython_include_dirs:
250                if i not in includes:
251                    includes.append(i)
252        except AttributeError:
253            pass
254
255        # In case extension.include_dirs is a generator, evaluate it and keep
256        # result
257        extension.include_dirs = list(extension.include_dirs)
258        for i in extension.include_dirs:
259            if i not in includes:
260                includes.append(i)
261
262        # Set up Cython compiler directives:
263        #    1. Start with the command line option.
264        #    2. Add in any (unique) entries from the extension
265        #         cython_directives (if Cython.Distutils.extension is used).
266        directives = dict(self.cython_directives)
267        if hasattr(extension, "cython_directives"):
268            directives.update(extension.cython_directives)
269
270        # Set the target file extension for C/C++ mode.
271        if cplus:
272            target_ext = '.cpp'
273        else:
274            target_ext = '.c'
275
276        # Decide whether to drop the generated C files into the temp dir
277        # or the source tree.
278
279        if not self.inplace and (self.cython_c_in_temp
280                or getattr(extension, 'cython_c_in_temp', 0)):
281            target_dir = os.path.join(self.build_temp, "pyrex")
282            for package_name in extension.name.split('.')[:-1]:
283                target_dir = os.path.join(target_dir, package_name)
284        else:
285            target_dir = None
286
287        newest_dependency = None
288        for source in sources:
289            (base, ext) = os.path.splitext(os.path.basename(source))
290            if ext == ".py":
291                # FIXME: we might want to special case this some more
292                ext = '.pyx'
293            if ext == ".pyx":              # Cython source file
294                output_dir = target_dir or os.path.dirname(source)
295                new_sources.append(os.path.join(output_dir, base + target_ext))
296                cython_sources.append(source)
297                cython_targets[source] = new_sources[-1]
298            elif ext == '.pxi' or ext == '.pxd':
299                if newest_dependency is None \
300                        or newer(source, newest_dependency):
301                    newest_dependency = source
302            else:
303                new_sources.append(source)
304
305        if not cython_sources:
306            return new_sources
307
308        try:
309            from Cython.Compiler.Main \
310                import CompilationOptions, \
311                       default_options as cython_default_options, \
312                       compile as cython_compile
313            from Cython.Compiler.Errors import PyrexError
314        except ImportError:
315            e = sys.exc_info()[1]
316            print("failed to import Cython: %s" % e)
317            raise DistutilsPlatformError("Cython does not appear to be installed")
318
319        module_name = extension.name
320
321        for source in cython_sources:
322            target = cython_targets[source]
323            depends = [source] + list(extension.depends or ())
324            if(source[-4:].lower()==".pyx" and os.path.isfile(source[:-3]+"pxd")):
325                depends += [source[:-3]+"pxd"]
326            rebuild = self.force or newer_group(depends, target, 'newer')
327            if not rebuild and newest_dependency is not None:
328                rebuild = newer(newest_dependency, target)
329            if rebuild:
330                log.info("cythoning %s to %s", source, target)
331                self.mkpath(os.path.dirname(target))
332                if self.inplace:
333                    output_dir = os.curdir
334                else:
335                    output_dir = self.build_lib
336                options = CompilationOptions(cython_default_options,
337                    use_listing_file = create_listing,
338                    include_path = includes,
339                    compiler_directives = directives,
340                    output_file = target,
341                    cplus = cplus,
342                    emit_linenums = line_directives,
343                    c_line_in_traceback = not no_c_in_traceback,
344                    generate_pxi = cython_gen_pxi,
345                    output_dir = output_dir,
346                    gdb_debug = cython_gdb,
347                    compile_time_env = cython_compile_time_env)
348                result = cython_compile(source, options=options,
349                                        full_module_name=module_name)
350            else:
351                log.info("skipping '%s' Cython extension (up-to-date)", target)
352
353        return new_sources
354
355    # cython_sources ()
356
357# class build_ext
358