| """ |
| Highly experimental script that compiles the CPython standard library using Cython. |
| |
| Execute the script either in the CPython 'Lib' directory or pass the |
| option '--current-python' to compile the standard library of the running |
| Python interpreter. |
| |
| Pass '-j N' to get a parallel build with N processes. |
| |
| Usage example:: |
| |
| $ python cystdlib.py --current-python build_ext -i |
| """ |
| |
| import os |
| import sys |
| from distutils.core import setup |
| from Cython.Build import cythonize |
| from Cython.Compiler import Options |
| |
| # improve Python compatibility by allowing some broken code |
| Options.error_on_unknown_names = False |
| Options.error_on_uninitialized = False |
| |
| exclude_patterns = ['**/test/**/*.py', '**/tests/**/*.py', '**/__init__.py'] |
| broken = [ |
| 'idlelib/MultiCall.py', |
| 'email/utils.py', |
| 'multiprocessing/reduction.py', |
| 'multiprocessing/util.py', |
| 'threading.py', # interrupt handling |
| 'lib2to3/fixes/fix_sys_exc.py', |
| 'traceback.py', |
| 'types.py', |
| 'enum.py', |
| 'importlib/_bootstrap', |
| ] |
| |
| default_directives = dict( |
| auto_cpdef=False, # enable when it's safe, see long list of failures below |
| binding=True, |
| set_initial_path='SOURCEFILE') |
| default_directives['optimize.inline_defnode_calls'] = True |
| |
| special_directives = [ |
| (['pkgutil.py', |
| 'decimal.py', |
| 'datetime.py', |
| 'optparse.py', |
| 'sndhdr.py', |
| 'opcode.py', |
| 'ntpath.py', |
| 'urllib/request.py', |
| 'plat-*/TYPES.py', |
| 'plat-*/IN.py', |
| 'tkinter/_fix.py', |
| 'lib2to3/refactor.py', |
| 'webbrowser.py', |
| 'shutil.py', |
| 'multiprocessing/forking.py', |
| 'xml/sax/expatreader.py', |
| 'xmlrpc/client.py', |
| 'pydoc.py', |
| 'xml/etree/ElementTree.py', |
| 'posixpath.py', |
| 'inspect.py', |
| 'ctypes/util.py', |
| 'urllib/parse.py', |
| 'warnings.py', |
| 'tempfile.py', |
| 'trace.py', |
| 'heapq.py', |
| 'pickletools.py', |
| 'multiprocessing/connection.py', |
| 'hashlib.py', |
| 'getopt.py', |
| 'os.py', |
| 'types.py', |
| ], dict(auto_cpdef=False)), |
| ] |
| del special_directives[:] # currently unused |
| |
| def build_extensions(includes='**/*.py', |
| excludes=None, |
| special_directives=special_directives, |
| language_level=sys.version_info[0], |
| parallel=None): |
| if isinstance(includes, str): |
| includes = [includes] |
| excludes = list(excludes or exclude_patterns) + broken |
| |
| all_groups = (special_directives or []) + [(includes, {})] |
| extensions = [] |
| for modules, directives in all_groups: |
| exclude_now = excludes[:] |
| for other_modules, _ in special_directives: |
| if other_modules != modules: |
| exclude_now.extend(other_modules) |
| |
| d = dict(default_directives) |
| d.update(directives) |
| |
| extensions.extend( |
| cythonize( |
| modules, |
| exclude=exclude_now, |
| exclude_failures=True, |
| language_level=language_level, |
| compiler_directives=d, |
| nthreads=parallel, |
| )) |
| return extensions |
| |
| |
| def build(extensions): |
| try: |
| setup(ext_modules=extensions) |
| result = True |
| except: |
| import traceback |
| print('error building extensions %s' % ( |
| [ext.name for ext in extensions],)) |
| traceback.print_exc() |
| result = False |
| return extensions, result |
| |
| |
| def _build(args): |
| sys_args, ext = args |
| sys.argv[1:] = sys_args |
| return build([ext]) |
| |
| |
| def parse_args(): |
| from optparse import OptionParser |
| parser = OptionParser('%prog [options] [LIB_DIR (default: ./Lib)]') |
| parser.add_option( |
| '--current-python', dest='current_python', action='store_true', |
| help='compile the stdlib of the running Python') |
| parser.add_option( |
| '-j', '--jobs', dest='parallel_jobs', metavar='N', |
| type=int, default=1, |
| help='run builds in N parallel jobs (default: 1)') |
| parser.add_option( |
| '-x', '--exclude', dest='excludes', metavar='PATTERN', |
| action="append", help='exclude modules/packages matching PATTERN') |
| options, args = parser.parse_args() |
| if not args: |
| args = ['./Lib'] |
| elif len(args) > 1: |
| parser.error('only one argument expected, got %d' % len(args)) |
| return options, args |
| |
| |
| if __name__ == '__main__': |
| options, args = parse_args() |
| if options.current_python: |
| # assume that the stdlib is where the "os" module lives |
| os.chdir(os.path.dirname(os.__file__)) |
| else: |
| os.chdir(args[0]) |
| |
| pool = None |
| parallel_jobs = options.parallel_jobs |
| if options.parallel_jobs: |
| try: |
| import multiprocessing |
| pool = multiprocessing.Pool(parallel_jobs) |
| print("Building in %d parallel processes" % parallel_jobs) |
| except (ImportError, OSError): |
| print("Not building in parallel") |
| parallel_jobs = 0 |
| |
| extensions = build_extensions( |
| parallel=parallel_jobs, |
| excludes=options.excludes) |
| sys_args = ['build_ext', '-i'] |
| if pool is not None: |
| results = pool.map(_build, [(sys_args, ext) for ext in extensions]) |
| pool.close() |
| pool.join() |
| for ext, result in results: |
| if not result: |
| print("building extension %s failed" % (ext[0].name,)) |
| else: |
| sys.argv[1:] = sys_args |
| build(extensions) |