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