Home | History | Annotate | Download | only in Build
      1 #!/usr/bin/env python
      2 
      3 import os
      4 import shutil
      5 import tempfile
      6 
      7 from distutils.core import setup
      8 from Cython.Build.Dependencies import cythonize, extended_iglob
      9 from Cython.Utils import is_package_dir
     10 from Cython.Compiler import Options
     11 
     12 try:
     13     import multiprocessing
     14     parallel_compiles = int(multiprocessing.cpu_count() * 1.5)
     15 except ImportError:
     16     multiprocessing = None
     17     parallel_compiles = 0
     18 
     19 
     20 class _FakePool(object):
     21     def map_async(self, func, args):
     22         from itertools import imap
     23         for _ in imap(func, args):
     24             pass
     25 
     26     def close(self): pass
     27     def terminate(self): pass
     28     def join(self): pass
     29 
     30 
     31 def parse_directives(option, name, value, parser):
     32     dest = option.dest
     33     old_directives = dict(getattr(parser.values, dest,
     34                                   Options.directive_defaults))
     35     directives = Options.parse_directive_list(
     36         value, relaxed_bool=True, current_settings=old_directives)
     37     setattr(parser.values, dest, directives)
     38 
     39 
     40 def parse_options(option, name, value, parser):
     41     dest = option.dest
     42     options = dict(getattr(parser.values, dest, {}))
     43     for opt in value.split(','):
     44         if '=' in opt:
     45             n, v = opt.split('=', 1)
     46             v = v.lower() not in ('false', 'f', '0', 'no')
     47         else:
     48             n, v = opt, True
     49         options[n] = v
     50     setattr(parser.values, dest, options)
     51 
     52 
     53 def find_package_base(path):
     54     base_dir, package_path = os.path.split(path)
     55     while os.path.isfile(os.path.join(base_dir, '__init__.py')):
     56         base_dir, parent = os.path.split(base_dir)
     57         package_path = '%s/%s' % (parent, package_path)
     58     return base_dir, package_path
     59 
     60 
     61 def cython_compile(path_pattern, options):
     62     pool = None
     63     paths = map(os.path.abspath, extended_iglob(path_pattern))
     64     try:
     65         for path in paths:
     66             if options.build_inplace:
     67                 base_dir = path
     68                 while not os.path.isdir(base_dir) or is_package_dir(base_dir):
     69                     base_dir = os.path.dirname(base_dir)
     70             else:
     71                 base_dir = None
     72 
     73             if os.path.isdir(path):
     74                 # recursively compiling a package
     75                 paths = [os.path.join(path, '**', '*.%s' % ext)
     76                          for ext in ('py', 'pyx')]
     77             else:
     78                 # assume it's a file(-like thing)
     79                 paths = [path]
     80 
     81             ext_modules = cythonize(
     82                 paths,
     83                 nthreads=options.parallel,
     84                 exclude_failures=options.keep_going,
     85                 exclude=options.excludes,
     86                 compiler_directives=options.directives,
     87                 force=options.force,
     88                 quiet=options.quiet,
     89                 **options.options)
     90 
     91             if ext_modules and options.build:
     92                 if len(ext_modules) > 1 and options.parallel > 1:
     93                     if pool is None:
     94                         try:
     95                             pool = multiprocessing.Pool(options.parallel)
     96                         except OSError:
     97                             pool = _FakePool()
     98                     pool.map_async(run_distutils, [
     99                         (base_dir, [ext]) for ext in ext_modules])
    100                 else:
    101                     run_distutils((base_dir, ext_modules))
    102     except:
    103         if pool is not None:
    104             pool.terminate()
    105         raise
    106     else:
    107         if pool is not None:
    108             pool.close()
    109             pool.join()
    110 
    111 
    112 def run_distutils(args):
    113     base_dir, ext_modules = args
    114     script_args = ['build_ext', '-i']
    115     cwd = os.getcwd()
    116     temp_dir = None
    117     try:
    118         if base_dir:
    119             os.chdir(base_dir)
    120             temp_dir = tempfile.mkdtemp(dir=base_dir)
    121             script_args.extend(['--build-temp', temp_dir])
    122         setup(
    123             script_name='setup.py',
    124             script_args=script_args,
    125             ext_modules=ext_modules,
    126         )
    127     finally:
    128         if base_dir:
    129             os.chdir(cwd)
    130             if temp_dir and os.path.isdir(temp_dir):
    131                 shutil.rmtree(temp_dir)
    132 
    133 
    134 def parse_args(args):
    135     from optparse import OptionParser
    136     parser = OptionParser(usage='%prog [options] [sources and packages]+')
    137 
    138     parser.add_option('-X', '--directive', metavar='NAME=VALUE,...', dest='directives',
    139                       type=str, action='callback', callback=parse_directives, default={},
    140                       help='set a compiler directive')
    141     parser.add_option('-s', '--option', metavar='NAME=VALUE', dest='options',
    142                       type=str, action='callback', callback=parse_options, default={},
    143                       help='set a cythonize option')
    144     parser.add_option('-3', dest='python3_mode', action='store_true',
    145                       help='use Python 3 syntax mode by default')
    146 
    147     parser.add_option('-x', '--exclude', metavar='PATTERN', dest='excludes',
    148                       action='append', default=[],
    149                       help='exclude certain file patterns from the compilation')
    150 
    151     parser.add_option('-b', '--build', dest='build', action='store_true',
    152                       help='build extension modules using distutils')
    153     parser.add_option('-i', '--inplace', dest='build_inplace', action='store_true',
    154                       help='build extension modules in place using distutils (implies -b)')
    155     parser.add_option('-j', '--parallel', dest='parallel', metavar='N',
    156                       type=int, default=parallel_compiles,
    157                       help=('run builds in N parallel jobs (default: %d)' %
    158                             parallel_compiles or 1))
    159     parser.add_option('-f', '--force', dest='force', action='store_true',
    160                       help='force recompilation')
    161     parser.add_option('-q', '--quiet', dest='quiet', action='store_true',
    162                       help='be less verbose during compilation')
    163 
    164     parser.add_option('--lenient', dest='lenient', action='store_true',
    165                       help='increase Python compatibility by ignoring some compile time errors')
    166     parser.add_option('-k', '--keep-going', dest='keep_going', action='store_true',
    167                       help='compile as much as possible, ignore compilation failures')
    168 
    169     options, args = parser.parse_args(args)
    170     if not args:
    171         parser.error("no source files provided")
    172     if options.build_inplace:
    173         options.build = True
    174     if multiprocessing is None:
    175         options.parallel = 0
    176     if options.python3_mode:
    177         options.options['language_level'] = 3
    178     return options, args
    179 
    180 
    181 def main(args=None):
    182     options, paths = parse_args(args)
    183 
    184     if options.lenient:
    185         # increase Python compatibility by ignoring compile time errors
    186         Options.error_on_unknown_names = False
    187         Options.error_on_uninitialized = False
    188 
    189     for path in paths:
    190         cython_compile(path, options)
    191 
    192 
    193 if __name__ == '__main__':
    194     main()
    195