#!/usr/bin/env python from __future__ import absolute_import import os import shutil import tempfile from distutils.core import setup from .Dependencies import cythonize, extended_iglob from ..Utils import is_package_dir from ..Compiler import Options try: import multiprocessing parallel_compiles = int(multiprocessing.cpu_count() * 1.5) except ImportError: multiprocessing = None parallel_compiles = 0 class _FakePool(object): def map_async(self, func, args): try: from itertools import imap except ImportError: imap=map for _ in imap(func, args): pass def close(self): pass def terminate(self): pass def join(self): pass def parse_directives(option, name, value, parser): dest = option.dest old_directives = dict(getattr(parser.values, dest, Options.get_directive_defaults())) directives = Options.parse_directive_list( value, relaxed_bool=True, current_settings=old_directives) setattr(parser.values, dest, directives) def parse_options(option, name, value, parser): dest = option.dest options = dict(getattr(parser.values, dest, {})) for opt in value.split(','): if '=' in opt: n, v = opt.split('=', 1) v = v.lower() not in ('false', 'f', '0', 'no') else: n, v = opt, True options[n] = v setattr(parser.values, dest, options) def parse_compile_time_env(option, name, value, parser): dest = option.dest old_env = dict(getattr(parser.values, dest, {})) new_env = Options.parse_compile_time_env(value, current_settings=old_env) setattr(parser.values, dest, new_env) def find_package_base(path): base_dir, package_path = os.path.split(path) while os.path.isfile(os.path.join(base_dir, '__init__.py')): base_dir, parent = os.path.split(base_dir) package_path = '%s/%s' % (parent, package_path) return base_dir, package_path def cython_compile(path_pattern, options): pool = None all_paths = map(os.path.abspath, extended_iglob(path_pattern)) try: for path in all_paths: if options.build_inplace: base_dir = path while not os.path.isdir(base_dir) or is_package_dir(base_dir): base_dir = os.path.dirname(base_dir) else: base_dir = None if os.path.isdir(path): # recursively compiling a package paths = [os.path.join(path, '**', '*.{py,pyx}')] else: # assume it's a file(-like thing) paths = [path] ext_modules = cythonize( paths, nthreads=options.parallel, exclude_failures=options.keep_going, exclude=options.excludes, compiler_directives=options.directives, compile_time_env=options.compile_time_env, force=options.force, quiet=options.quiet, depfile=options.depfile, **options.options) if ext_modules and options.build: if len(ext_modules) > 1 and options.parallel > 1: if pool is None: try: pool = multiprocessing.Pool(options.parallel) except OSError: pool = _FakePool() pool.map_async(run_distutils, [ (base_dir, [ext]) for ext in ext_modules]) else: run_distutils((base_dir, ext_modules)) except: if pool is not None: pool.terminate() raise else: if pool is not None: pool.close() pool.join() def run_distutils(args): base_dir, ext_modules = args script_args = ['build_ext', '-i'] cwd = os.getcwd() temp_dir = None try: if base_dir: os.chdir(base_dir) temp_dir = tempfile.mkdtemp(dir=base_dir) script_args.extend(['--build-temp', temp_dir]) setup( script_name='setup.py', script_args=script_args, ext_modules=ext_modules, ) finally: if base_dir: os.chdir(cwd) if temp_dir and os.path.isdir(temp_dir): shutil.rmtree(temp_dir) def parse_args(args): from optparse import OptionParser parser = OptionParser(usage='%prog [options] [sources and packages]+') parser.add_option('-X', '--directive', metavar='NAME=VALUE,...', dest='directives', default={}, type="str", action='callback', callback=parse_directives, help='set a compiler directive') parser.add_option('-E', '--compile-time-env', metavar='NAME=VALUE,...', dest='compile_time_env', default={}, type="str", action='callback', callback=parse_compile_time_env, help='set a compile time environment variable') parser.add_option('-s', '--option', metavar='NAME=VALUE', dest='options', default={}, type="str", action='callback', callback=parse_options, help='set a cythonize option') parser.add_option('-2', dest='language_level', action='store_const', const=2, default=None, help='use Python 2 syntax mode by default') parser.add_option('-3', dest='language_level', action='store_const', const=3, help='use Python 3 syntax mode by default') parser.add_option('--3str', dest='language_level', action='store_const', const='3str', help='use Python 3 syntax mode by default') parser.add_option('-a', '--annotate', dest='annotate', action='store_true', help='generate annotated HTML page for source files') parser.add_option('-x', '--exclude', metavar='PATTERN', dest='excludes', action='append', default=[], help='exclude certain file patterns from the compilation') parser.add_option('-b', '--build', dest='build', action='store_true', help='build extension modules using distutils') parser.add_option('-i', '--inplace', dest='build_inplace', action='store_true', help='build extension modules in place using distutils (implies -b)') parser.add_option('-j', '--parallel', dest='parallel', metavar='N', type=int, default=parallel_compiles, help=('run builds in N parallel jobs (default: %d)' % parallel_compiles or 1)) parser.add_option('-f', '--force', dest='force', action='store_true', help='force recompilation') parser.add_option('-q', '--quiet', dest='quiet', action='store_true', help='be less verbose during compilation') parser.add_option('--lenient', dest='lenient', action='store_true', help='increase Python compatibility by ignoring some compile time errors') parser.add_option('-k', '--keep-going', dest='keep_going', action='store_true', help='compile as much as possible, ignore compilation failures') parser.add_option('-M', '--depfile', action='store_true', help='produce depfiles for the sources') options, args = parser.parse_args(args) if not args: parser.error("no source files provided") if options.build_inplace: options.build = True if multiprocessing is None: options.parallel = 0 if options.language_level: assert options.language_level in (2, 3, '3str') options.options['language_level'] = options.language_level return options, args def main(args=None): options, paths = parse_args(args) if options.lenient: # increase Python compatibility by ignoring compile time errors Options.error_on_unknown_names = False Options.error_on_uninitialized = False if options.annotate: Options.annotate = True for path in paths: cython_compile(path, options) if __name__ == '__main__': main()