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