Blame Tools/cystdlib.py

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