patch for ticket #646 by klepa: provide dedicated switches for 'c_line_in_traceback...
[cython.git] / Cython / Distutils / build_ext.py
1 """Cython.Distutils.build_ext
2
3 Implements a version of the Distutils 'build_ext' command, for
4 building Cython extension modules."""
5
6 # This module should be kept compatible with Python 2.3.
7
8 __revision__ = "$Id:$"
9
10 import sys
11 import os
12 import re
13 from distutils.core import Command
14 from distutils.errors import DistutilsPlatformError
15 from distutils.sysconfig import customize_compiler, get_python_version
16 from distutils.dep_util import newer, newer_group
17 from distutils import log
18 from distutils.dir_util import mkpath
19 from distutils.command import build_ext as _build_ext
20 from distutils import sysconfig
21
22 extension_name_re = _build_ext.extension_name_re
23
24 show_compilers = _build_ext.show_compilers
25
26 class Optimization(object):
27     def __init__(self):
28         self.flags = (
29             'OPT',
30             'CFLAGS',
31             'CPPFLAGS',
32             'EXTRA_CFLAGS',
33             'BASECFLAGS',
34             'PY_CFLAGS',
35         )
36         self.state = sysconfig.get_config_vars(*self.flags)
37         self.config_vars = sysconfig.get_config_vars()
38
39
40     def disable_optimization(self):
41         "disable optimization for the C or C++ compiler"
42         badoptions = ('-O1', '-O2', '-O3')
43
44         for flag, option in zip(self.flags, self.state):
45             if option is not None:
46                 L = [opt for opt in option.split() if opt not in badoptions]
47                 self.config_vars[flag] = ' '.join(L)
48
49     def restore_state(self):
50         "restore the original state"
51         for flag, option in zip(self.flags, self.state):
52             if option is not None:
53                 self.config_vars[flag] = option
54
55
56 optimization = Optimization()
57
58 try:
59     any
60 except NameError:
61     def any(it):
62         for x in it:
63             if x:
64                 return True
65
66         return False
67
68
69 class build_ext(_build_ext.build_ext):
70
71     description = "build C/C++ and Cython extensions (compile/link to build directory)"
72
73     sep_by = _build_ext.build_ext.sep_by
74     user_options = _build_ext.build_ext.user_options
75     boolean_options = _build_ext.build_ext.boolean_options
76     help_options = _build_ext.build_ext.help_options
77
78     # Add the pyrex specific data.
79     user_options.extend([
80         ('pyrex-cplus', None,
81          "generate C++ source files"),
82         ('pyrex-create-listing', None,
83          "write errors to a listing file"),
84         ('pyrex-line-directives', None,
85          "emit source line directives"),
86         ('pyrex-include-dirs=', None,
87          "path to the Cython include files" + sep_by),
88         ('pyrex-c-in-temp', None,
89          "put generated C files in temp directory"),
90         ('pyrex-gen-pxi', None,
91             "generate .pxi file for public declarations"),
92         ('pyrex-directives=', None,
93             "compiler directive overrides"),
94         ('pyrex-gdb', None,
95          "generate debug information for cygdb"),
96         ])
97
98     boolean_options.extend([
99         'pyrex-cplus', 'pyrex-create-listing', 'pyrex-line-directives',
100         'pyrex-c-in-temp', 'pyrex-gdb',
101     ])
102
103     def initialize_options(self):
104         _build_ext.build_ext.initialize_options(self)
105         self.pyrex_cplus = 0
106         self.pyrex_create_listing = 0
107         self.pyrex_line_directives = 0
108         self.pyrex_include_dirs = None
109         self.pyrex_directives = None
110         self.pyrex_c_in_temp = 0
111         self.pyrex_gen_pxi = 0
112         self.pyrex_gdb = False
113         self.no_c_in_traceback = 0
114
115     def finalize_options (self):
116         _build_ext.build_ext.finalize_options(self)
117         if self.pyrex_include_dirs is None:
118             self.pyrex_include_dirs = []
119         elif isinstance(self.pyrex_include_dirs, basestring):
120             self.pyrex_include_dirs = \
121                 self.pyrex_include_dirs.split(os.pathsep)
122         if self.pyrex_directives is None:
123             self.pyrex_directives = {}
124     # finalize_options ()
125
126     def run(self):
127         # We have one shot at this before build_ext initializes the compiler.
128         # If --pyrex-gdb is in effect as a command line option or as option
129         # of any Extension module, disable optimization for the C or C++
130         # compiler.
131         if (self.pyrex_gdb or any([getattr(ext, 'pyrex_gdb', False)
132                                        for ext in self.extensions])):
133             optimization.disable_optimization()
134
135         _build_ext.build_ext.run(self)
136
137     def build_extensions(self):
138         # First, sanity-check the 'extensions' list
139         self.check_extensions_list(self.extensions)
140
141         for ext in self.extensions:
142             ext.sources = self.cython_sources(ext.sources, ext)
143             self.build_extension(ext)
144
145     def cython_sources(self, sources, extension):
146         """
147         Walk the list of source files in 'sources', looking for Cython
148         source files (.pyx and .py).  Run Cython on all that are
149         found, and return a modified 'sources' list with Cython source
150         files replaced by the generated C (or C++) files.
151         """
152         try:
153             from Cython.Compiler.Main \
154                 import CompilationOptions, \
155                        default_options as pyrex_default_options, \
156                        compile as cython_compile
157             from Cython.Compiler.Errors import PyrexError
158         except ImportError:
159             e = sys.exc_info()[1]
160             print("failed to import Cython: %s" % e)
161             raise DistutilsPlatformError("Cython does not appear to be installed")
162
163         new_sources = []
164         pyrex_sources = []
165         pyrex_targets = {}
166
167         # Setup create_list and cplus from the extension options if
168         # Cython.Distutils.extension.Extension is used, otherwise just
169         # use what was parsed from the command-line or the configuration file.
170         # cplus will also be set to true is extension.language is equal to
171         # 'C++' or 'c++'.
172         #try:
173         #    create_listing = self.pyrex_create_listing or \
174         #                        extension.pyrex_create_listing
175         #    cplus = self.pyrex_cplus or \
176         #                extension.pyrex_cplus or \
177         #                (extension.language != None and \
178         #                    extension.language.lower() == 'c++')
179         #except AttributeError:
180         #    create_listing = self.pyrex_create_listing
181         #    cplus = self.pyrex_cplus or \
182         #                (extension.language != None and \
183         #                    extension.language.lower() == 'c++')
184
185         create_listing = self.pyrex_create_listing or \
186             getattr(extension, 'pyrex_create_listing', 0)
187         line_directives = self.pyrex_line_directives or \
188             getattr(extension, 'pyrex_line_directives', 0)
189         no_c_in_traceback = self.no_c_in_traceback or \
190             getattr(extension, 'no_c_in_traceback', 0)
191         cplus = self.pyrex_cplus or getattr(extension, 'pyrex_cplus', 0) or \
192                 (extension.language and extension.language.lower() == 'c++')
193         pyrex_gen_pxi = self.pyrex_gen_pxi or getattr(extension, 'pyrex_gen_pxi', 0)
194         pyrex_gdb = self.pyrex_gdb or getattr(extension, 'pyrex_gdb', False)
195         # Set up the include_path for the Cython compiler:
196         #    1.    Start with the command line option.
197         #    2.    Add in any (unique) paths from the extension
198         #        pyrex_include_dirs (if Cython.Distutils.extension is used).
199         #    3.    Add in any (unique) paths from the extension include_dirs
200         includes = self.pyrex_include_dirs
201         try:
202             for i in extension.pyrex_include_dirs:
203                 if not i in includes:
204                     includes.append(i)
205         except AttributeError:
206             pass
207         for i in extension.include_dirs:
208             if not i in includes:
209                 includes.append(i)
210
211         # Set up Cython compiler directives:
212         #    1. Start with the command line option.
213         #    2. Add in any (unique) entries from the extension
214         #         pyrex_directives (if Cython.Distutils.extension is used).
215         directives = self.pyrex_directives
216         if hasattr(extension, "pyrex_directives"):
217             directives.update(extension.pyrex_directives)
218
219         # Set the target_ext to '.c'.  Cython will change this to '.cpp' if
220         # needed.
221         if cplus:
222             target_ext = '.cpp'
223         else:
224             target_ext = '.c'
225
226         # Decide whether to drop the generated C files into the temp dir
227         # or the source tree.
228
229         if not self.inplace and (self.pyrex_c_in_temp
230                 or getattr(extension, 'pyrex_c_in_temp', 0)):
231             target_dir = os.path.join(self.build_temp, "pyrex")
232             for package_name in extension.name.split('.')[:-1]:
233                 target_dir = os.path.join(target_dir, package_name)
234         else:
235             target_dir = None
236
237         newest_dependency = None
238         for source in sources:
239             (base, ext) = os.path.splitext(os.path.basename(source))
240             if ext == ".py":
241                 # FIXME: we might want to special case this some more
242                 ext = '.pyx'
243             if ext == ".pyx":              # Cython source file
244                 output_dir = target_dir or os.path.dirname(source)
245                 new_sources.append(os.path.join(output_dir, base + target_ext))
246                 pyrex_sources.append(source)
247                 pyrex_targets[source] = new_sources[-1]
248             elif ext == '.pxi' or ext == '.pxd':
249                 if newest_dependency is None \
250                         or newer(source, newest_dependency):
251                     newest_dependency = source
252             else:
253                 new_sources.append(source)
254
255         if not pyrex_sources:
256             return new_sources
257
258         module_name = extension.name
259
260         for source in pyrex_sources:
261             target = pyrex_targets[source]
262             depends = [source] + list(extension.depends or ())
263             rebuild = self.force or newer_group(depends, target, 'newer')
264             if not rebuild and newest_dependency is not None:
265                 rebuild = newer(newest_dependency, target)
266             if rebuild:
267                 log.info("cythoning %s to %s", source, target)
268                 self.mkpath(os.path.dirname(target))
269                 if self.inplace:
270                     output_dir = os.curdir
271                 else:
272                     output_dir = self.build_lib
273                 options = CompilationOptions(pyrex_default_options,
274                     use_listing_file = create_listing,
275                     include_path = includes,
276                     compiler_directives = directives,
277                     output_file = target,
278                     cplus = cplus,
279                     emit_linenums = line_directives,
280                     c_line_in_traceback = not no_c_in_traceback,
281                     generate_pxi = pyrex_gen_pxi,
282                     output_dir = output_dir,
283                     gdb_debug = pyrex_gdb)
284                 result = cython_compile(source, options=options,
285                                         full_module_name=module_name)
286             else:
287                 log.info("skipping '%s' Cython extension (up-to-date)", target)
288
289         return new_sources
290
291     # cython_sources ()
292
293 # class build_ext