Home | History | Annotate | Download | only in Distutils
      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 
     59 class build_ext(_build_ext.build_ext):
     60 
     61     description = "build C/C++ and Cython extensions (compile/link to build directory)"
     62 
     63     sep_by = _build_ext.build_ext.sep_by
     64     user_options = _build_ext.build_ext.user_options
     65     boolean_options = _build_ext.build_ext.boolean_options
     66     help_options = _build_ext.build_ext.help_options
     67 
     68     # Add the pyrex specific data.
     69     user_options.extend([
     70         ('cython-cplus', None,
     71          "generate C++ source files"),
     72         ('cython-create-listing', None,
     73          "write errors to a listing file"),
     74         ('cython-line-directives', None,
     75          "emit source line directives"),
     76         ('cython-include-dirs=', None,
     77          "path to the Cython include files" + sep_by),
     78         ('cython-c-in-temp', None,
     79          "put generated C files in temp directory"),
     80         ('cython-gen-pxi', None,
     81             "generate .pxi file for public declarations"),
     82         ('cython-directives=', None,
     83             "compiler directive overrides"),
     84         ('cython-gdb', None,
     85          "generate debug information for cygdb"),
     86         ('cython-compile-time-env', None,
     87             "cython compile time environment"),
     88             
     89         # For backwards compatibility.
     90         ('pyrex-cplus', None,
     91          "generate C++ source files"),
     92         ('pyrex-create-listing', None,
     93          "write errors to a listing file"),
     94         ('pyrex-line-directives', None,
     95          "emit source line directives"),
     96         ('pyrex-include-dirs=', None,
     97          "path to the Cython include files" + sep_by),
     98         ('pyrex-c-in-temp', None,
     99          "put generated C files in temp directory"),
    100         ('pyrex-gen-pxi', None,
    101             "generate .pxi file for public declarations"),
    102         ('pyrex-directives=', None,
    103             "compiler directive overrides"),
    104         ('pyrex-gdb', None,
    105          "generate debug information for cygdb"),
    106         ])
    107 
    108     boolean_options.extend([
    109         'cython-cplus', 'cython-create-listing', 'cython-line-directives',
    110         'cython-c-in-temp', 'cython-gdb',
    111         
    112         # For backwards compatibility.
    113         'pyrex-cplus', 'pyrex-create-listing', 'pyrex-line-directives',
    114         'pyrex-c-in-temp', 'pyrex-gdb',
    115     ])
    116 
    117     def initialize_options(self):
    118         _build_ext.build_ext.initialize_options(self)
    119         self.cython_cplus = 0
    120         self.cython_create_listing = 0
    121         self.cython_line_directives = 0
    122         self.cython_include_dirs = None
    123         self.cython_directives = None
    124         self.cython_c_in_temp = 0
    125         self.cython_gen_pxi = 0
    126         self.cython_gdb = False
    127         self.no_c_in_traceback = 0
    128         self.cython_compile_time_env = None
    129     
    130     def __getattr__(self, name):
    131         if name[:6] == 'pyrex_':
    132             return getattr(self, 'cython_' + name[6:])
    133         else:
    134             return _build_ext.build_ext.__getattr__(self, name)
    135 
    136     def __setattr__(self, name, value):
    137         if name[:6] == 'pyrex_':
    138             return setattr(self, 'cython_' + name[6:], value)
    139         else:
    140             # _build_ext.build_ext.__setattr__(self, name, value)
    141             self.__dict__[name] = value
    142 
    143     def finalize_options (self):
    144         _build_ext.build_ext.finalize_options(self)
    145         if self.cython_include_dirs is None:
    146             self.cython_include_dirs = []
    147         elif isinstance(self.cython_include_dirs, basestring):
    148             self.cython_include_dirs = \
    149                 self.cython_include_dirs.split(os.pathsep)
    150         if self.cython_directives is None:
    151             self.cython_directives = {}
    152     # finalize_options ()
    153 
    154     def run(self):
    155         # We have one shot at this before build_ext initializes the compiler.
    156         # If --pyrex-gdb is in effect as a command line option or as option
    157         # of any Extension module, disable optimization for the C or C++
    158         # compiler.
    159         if self.cython_gdb or [1 for ext in self.extensions
    160                                      if getattr(ext, 'cython_gdb', False)]:
    161             optimization.disable_optimization()
    162 
    163         _build_ext.build_ext.run(self)
    164 
    165     def build_extensions(self):
    166         # First, sanity-check the 'extensions' list
    167         self.check_extensions_list(self.extensions)
    168 
    169         for ext in self.extensions:
    170             ext.sources = self.cython_sources(ext.sources, ext)
    171             self.build_extension(ext)
    172 
    173     def cython_sources(self, sources, extension):
    174         """
    175         Walk the list of source files in 'sources', looking for Cython
    176         source files (.pyx and .py).  Run Cython on all that are
    177         found, and return a modified 'sources' list with Cython source
    178         files replaced by the generated C (or C++) files.
    179         """
    180         try:
    181             from Cython.Compiler.Main \
    182                 import CompilationOptions, \
    183                        default_options as cython_default_options, \
    184                        compile as cython_compile
    185             from Cython.Compiler.Errors import PyrexError
    186         except ImportError:
    187             e = sys.exc_info()[1]
    188             print("failed to import Cython: %s" % e)
    189             raise DistutilsPlatformError("Cython does not appear to be installed")
    190 
    191         new_sources = []
    192         cython_sources = []
    193         cython_targets = {}
    194 
    195         # Setup create_list and cplus from the extension options if
    196         # Cython.Distutils.extension.Extension is used, otherwise just
    197         # use what was parsed from the command-line or the configuration file.
    198         # cplus will also be set to true is extension.language is equal to
    199         # 'C++' or 'c++'.
    200         #try:
    201         #    create_listing = self.cython_create_listing or \
    202         #                        extension.cython_create_listing
    203         #    cplus = self.cython_cplus or \
    204         #                extension.cython_cplus or \
    205         #                (extension.language != None and \
    206         #                    extension.language.lower() == 'c++')
    207         #except AttributeError:
    208         #    create_listing = self.cython_create_listing
    209         #    cplus = self.cython_cplus or \
    210         #                (extension.language != None and \
    211         #                    extension.language.lower() == 'c++')
    212 
    213         create_listing = self.cython_create_listing or \
    214             getattr(extension, 'cython_create_listing', 0)
    215         line_directives = self.cython_line_directives or \
    216             getattr(extension, 'cython_line_directives', 0)
    217         no_c_in_traceback = self.no_c_in_traceback or \
    218             getattr(extension, 'no_c_in_traceback', 0)
    219         cplus = self.cython_cplus or getattr(extension, 'cython_cplus', 0) or \
    220                 (extension.language and extension.language.lower() == 'c++')
    221         cython_gen_pxi = self.cython_gen_pxi or getattr(extension, 'cython_gen_pxi', 0)
    222         cython_gdb = self.cython_gdb or getattr(extension, 'cython_gdb', False)
    223         cython_compile_time_env = self.cython_compile_time_env or \
    224             getattr(extension, 'cython_compile_time_env', None)
    225 
    226         # Set up the include_path for the Cython compiler:
    227         #    1.    Start with the command line option.
    228         #    2.    Add in any (unique) paths from the extension
    229         #        cython_include_dirs (if Cython.Distutils.extension is used).
    230         #    3.    Add in any (unique) paths from the extension include_dirs
    231         includes = self.cython_include_dirs
    232         try:
    233             for i in extension.cython_include_dirs:
    234                 if not i in includes:
    235                     includes.append(i)
    236         except AttributeError:
    237             pass
    238         for i in extension.include_dirs:
    239             if not i in includes:
    240                 includes.append(i)
    241 
    242         # Set up Cython compiler directives:
    243         #    1. Start with the command line option.
    244         #    2. Add in any (unique) entries from the extension
    245         #         cython_directives (if Cython.Distutils.extension is used).
    246         directives = self.cython_directives
    247         if hasattr(extension, "cython_directives"):
    248             directives.update(extension.cython_directives)
    249 
    250         # Set the target_ext to '.c'.  Cython will change this to '.cpp' if
    251         # needed.
    252         if cplus:
    253             target_ext = '.cpp'
    254         else:
    255             target_ext = '.c'
    256 
    257         # Decide whether to drop the generated C files into the temp dir
    258         # or the source tree.
    259 
    260         if not self.inplace and (self.cython_c_in_temp
    261                 or getattr(extension, 'cython_c_in_temp', 0)):
    262             target_dir = os.path.join(self.build_temp, "pyrex")
    263             for package_name in extension.name.split('.')[:-1]:
    264                 target_dir = os.path.join(target_dir, package_name)
    265         else:
    266             target_dir = None
    267 
    268         newest_dependency = None
    269         for source in sources:
    270             (base, ext) = os.path.splitext(os.path.basename(source))
    271             if ext == ".py":
    272                 # FIXME: we might want to special case this some more
    273                 ext = '.pyx'
    274             if ext == ".pyx":              # Cython source file
    275                 output_dir = target_dir or os.path.dirname(source)
    276                 new_sources.append(os.path.join(output_dir, base + target_ext))
    277                 cython_sources.append(source)
    278                 cython_targets[source] = new_sources[-1]
    279             elif ext == '.pxi' or ext == '.pxd':
    280                 if newest_dependency is None \
    281                         or newer(source, newest_dependency):
    282                     newest_dependency = source
    283             else:
    284                 new_sources.append(source)
    285 
    286         if not cython_sources:
    287             return new_sources
    288 
    289         module_name = extension.name
    290 
    291         for source in cython_sources:
    292             target = cython_targets[source]
    293             depends = [source] + list(extension.depends or ())
    294             if(source[-4:].lower()==".pyx" and os.path.isfile(source[:-3]+"pxd")):
    295                 depends += [source[:-3]+"pxd"]
    296             rebuild = self.force or newer_group(depends, target, 'newer')
    297             if not rebuild and newest_dependency is not None:
    298                 rebuild = newer(newest_dependency, target)
    299             if rebuild:
    300                 log.info("cythoning %s to %s", source, target)
    301                 self.mkpath(os.path.dirname(target))
    302                 if self.inplace:
    303                     output_dir = os.curdir
    304                 else:
    305                     output_dir = self.build_lib
    306                 options = CompilationOptions(cython_default_options,
    307                     use_listing_file = create_listing,
    308                     include_path = includes,
    309                     compiler_directives = directives,
    310                     output_file = target,
    311                     cplus = cplus,
    312                     emit_linenums = line_directives,
    313                     c_line_in_traceback = not no_c_in_traceback,
    314                     generate_pxi = cython_gen_pxi,
    315                     output_dir = output_dir,
    316                     gdb_debug = cython_gdb,
    317                     compile_time_env = cython_compile_time_env)
    318                 result = cython_compile(source, options=options,
    319                                         full_module_name=module_name)
    320             else:
    321                 log.info("skipping '%s' Cython extension (up-to-date)", target)
    322 
    323         return new_sources
    324 
    325     # cython_sources ()
    326 
    327 # class build_ext
    328