Blame SOURCES/compileall2.py

51cf4a
"""Module/script to byte-compile all .py files to .pyc files.
51cf4a
51cf4a
When called as a script with arguments, this compiles the directories
51cf4a
given as arguments recursively; the -l option prevents it from
51cf4a
recursing into directories.
51cf4a
51cf4a
Without arguments, if compiles all modules on sys.path, without
51cf4a
recursing into subdirectories.  (Even though it should do so for
51cf4a
packages -- for now, you'll have to deal with packages separately.)
51cf4a
51cf4a
See module py_compile for details of the actual byte-compilation.
51cf4a
51cf4a
License:
51cf4a
Compileall2 is an enhanced copy of Python's compileall module
51cf4a
and it follows Python licensing. For more info see: https://www.python.org/psf/license/
51cf4a
"""
51cf4a
import os
51cf4a
import sys
51cf4a
import importlib.util
51cf4a
import py_compile
51cf4a
import struct
51cf4a
import filecmp
51cf4a
51cf4a
from functools import partial
51cf4a
from pathlib import Path
51cf4a
51cf4a
# Python 3.7 and higher
51cf4a
PY37 = sys.version_info[0:2] >= (3, 7)
51cf4a
# Python 3.6 and higher
51cf4a
PY36 = sys.version_info[0:2] >= (3, 6)
51cf4a
# Python 3.5 and higher
51cf4a
PY35 = sys.version_info[0:2] >= (3, 5)
51cf4a
51cf4a
# Python 3.7 and above has a different structure and length
51cf4a
# of pyc files header. Also, multiple ways how to invalidate pyc file was
51cf4a
# introduced in Python 3.7. These cases are covered by variables here or by PY37
51cf4a
# variable itself.
51cf4a
if PY37:
51cf4a
    pyc_struct_format = '<4sll'
51cf4a
    pyc_header_lenght = 12
51cf4a
    pyc_header_format = (pyc_struct_format, importlib.util.MAGIC_NUMBER, 0)
51cf4a
else:
51cf4a
    pyc_struct_format = '<4sl'
51cf4a
    pyc_header_lenght = 8
51cf4a
    pyc_header_format = (pyc_struct_format, importlib.util.MAGIC_NUMBER)
51cf4a
51cf4a
__all__ = ["compile_dir","compile_file","compile_path"]
51cf4a
51cf4a
def optimization_kwarg(opt):
51cf4a
    """Returns opt as a dictionary {optimization: opt} for use as **kwarg
51cf4a
    for Python >= 3.5 and empty dictionary for Python 3.4"""
51cf4a
    if PY35:
51cf4a
        return dict(optimization=opt)
51cf4a
    else:
51cf4a
        # `debug_override` is a way how to enable optimized byte-compiled files
51cf4a
        # (.pyo) in Python <= 3.4
51cf4a
        if opt:
51cf4a
            return dict(debug_override=False)
51cf4a
        else:
51cf4a
            return dict()
51cf4a
51cf4a
def _walk_dir(dir, maxlevels, quiet=0):
51cf4a
    if PY36 and quiet < 2 and isinstance(dir, os.PathLike):
51cf4a
        dir = os.fspath(dir)
51cf4a
    else:
51cf4a
        dir = str(dir)
51cf4a
    if not quiet:
51cf4a
        print('Listing {!r}...'.format(dir))
51cf4a
    try:
51cf4a
        names = os.listdir(dir)
51cf4a
    except OSError:
51cf4a
        if quiet < 2:
51cf4a
            print("Can't list {!r}".format(dir))
51cf4a
        names = []
51cf4a
    names.sort()
51cf4a
    for name in names:
51cf4a
        if name == '__pycache__':
51cf4a
            continue
51cf4a
        fullname = os.path.join(dir, name)
51cf4a
        if not os.path.isdir(fullname):
51cf4a
            yield fullname
51cf4a
        elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
51cf4a
              os.path.isdir(fullname) and not os.path.islink(fullname)):
51cf4a
            yield from _walk_dir(fullname, maxlevels=maxlevels - 1,
51cf4a
                                 quiet=quiet)
51cf4a
51cf4a
def compile_dir(dir, maxlevels=None, ddir=None, force=False,
51cf4a
                rx=None, quiet=0, legacy=False, optimize=-1, workers=1,
51cf4a
                invalidation_mode=None, stripdir=None,
51cf4a
                prependdir=None, limit_sl_dest=None, hardlink_dupes=False):
51cf4a
    """Byte-compile all modules in the given directory tree.
51cf4a
51cf4a
    Arguments (only dir is required):
51cf4a
51cf4a
    dir:       the directory to byte-compile
51cf4a
    maxlevels: maximum recursion level (default `sys.getrecursionlimit()`)
51cf4a
    ddir:      the directory that will be prepended to the path to the
51cf4a
               file as it is compiled into each byte-code file.
51cf4a
    force:     if True, force compilation, even if timestamps are up-to-date
51cf4a
    quiet:     full output with False or 0, errors only with 1,
51cf4a
               no output with 2
51cf4a
    legacy:    if True, produce legacy pyc paths instead of PEP 3147 paths
51cf4a
    optimize:  int or list of optimization levels or -1 for level of
51cf4a
               the interpreter. Multiple levels leads to multiple compiled
51cf4a
               files each with one optimization level.
51cf4a
    workers:   maximum number of parallel workers
51cf4a
    invalidation_mode: how the up-to-dateness of the pyc will be checked
51cf4a
    stripdir:  part of path to left-strip from source file path
51cf4a
    prependdir: path to prepend to beggining of original file path, applied
51cf4a
               after stripdir
51cf4a
    limit_sl_dest: ignore symlinks if they are pointing outside of
51cf4a
                   the defined path
51cf4a
    hardlink_dupes: hardlink duplicated pyc files
51cf4a
    """
51cf4a
    ProcessPoolExecutor = None
51cf4a
    if ddir is not None and (stripdir is not None or prependdir is not None):
51cf4a
        raise ValueError(("Destination dir (ddir) cannot be used "
51cf4a
                          "in combination with stripdir or prependdir"))
51cf4a
    if ddir is not None:
51cf4a
        stripdir = dir
51cf4a
        prependdir = ddir
51cf4a
        ddir = None
51cf4a
    if workers is not None:
51cf4a
        if workers < 0:
51cf4a
            raise ValueError('workers must be greater or equal to 0')
51cf4a
        elif workers != 1:
51cf4a
            try:
51cf4a
                # Only import when needed, as low resource platforms may
51cf4a
                # fail to import it
51cf4a
                from concurrent.futures import ProcessPoolExecutor
51cf4a
            except ImportError:
51cf4a
                workers = 1
51cf4a
    if maxlevels is None:
51cf4a
        maxlevels = sys.getrecursionlimit()
51cf4a
    files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels)
51cf4a
    success = True
51cf4a
    if workers is not None and workers != 1 and ProcessPoolExecutor is not None:
51cf4a
        workers = workers or None
51cf4a
        with ProcessPoolExecutor(max_workers=workers) as executor:
51cf4a
            results = executor.map(partial(compile_file,
51cf4a
                                           ddir=ddir, force=force,
51cf4a
                                           rx=rx, quiet=quiet,
51cf4a
                                           legacy=legacy,
51cf4a
                                           optimize=optimize,
51cf4a
                                           invalidation_mode=invalidation_mode,
51cf4a
                                           stripdir=stripdir,
51cf4a
                                           prependdir=prependdir,
51cf4a
                                           limit_sl_dest=limit_sl_dest),
51cf4a
                                   files)
51cf4a
            success = min(results, default=True)
51cf4a
    else:
51cf4a
        for file in files:
51cf4a
            if not compile_file(file, ddir, force, rx, quiet,
51cf4a
                                legacy, optimize, invalidation_mode,
51cf4a
                                stripdir=stripdir, prependdir=prependdir,
51cf4a
                                limit_sl_dest=limit_sl_dest,
51cf4a
                                hardlink_dupes=hardlink_dupes):
51cf4a
                success = False
51cf4a
    return success
51cf4a
51cf4a
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
51cf4a
                 legacy=False, optimize=-1,
51cf4a
                 invalidation_mode=None, stripdir=None, prependdir=None,
51cf4a
                 limit_sl_dest=None, hardlink_dupes=False):
51cf4a
    """Byte-compile one file.
51cf4a
51cf4a
    Arguments (only fullname is required):
51cf4a
51cf4a
    fullname:  the file to byte-compile
51cf4a
    ddir:      if given, the directory name compiled in to the
51cf4a
               byte-code file.
51cf4a
    force:     if True, force compilation, even if timestamps are up-to-date
51cf4a
    quiet:     full output with False or 0, errors only with 1,
51cf4a
               no output with 2
51cf4a
    legacy:    if True, produce legacy pyc paths instead of PEP 3147 paths
51cf4a
    optimize:  int or list of optimization levels or -1 for level of
51cf4a
               the interpreter. Multiple levels leads to multiple compiled
51cf4a
               files each with one optimization level.
51cf4a
    invalidation_mode: how the up-to-dateness of the pyc will be checked
51cf4a
    stripdir:  part of path to left-strip from source file path
51cf4a
    prependdir: path to prepend to beggining of original file path, applied
51cf4a
               after stripdir
51cf4a
    limit_sl_dest: ignore symlinks if they are pointing outside of
51cf4a
                   the defined path.
51cf4a
    hardlink_dupes: hardlink duplicated pyc files
51cf4a
    """
51cf4a
51cf4a
    if ddir is not None and (stripdir is not None or prependdir is not None):
51cf4a
        raise ValueError(("Destination dir (ddir) cannot be used "
51cf4a
                          "in combination with stripdir or prependdir"))
51cf4a
51cf4a
    success = True
51cf4a
    if PY36 and quiet < 2 and isinstance(fullname, os.PathLike):
51cf4a
        fullname = os.fspath(fullname)
51cf4a
    else:
51cf4a
        fullname = str(fullname)
51cf4a
    name = os.path.basename(fullname)
51cf4a
51cf4a
    dfile = None
51cf4a
51cf4a
    if ddir is not None:
51cf4a
        if not PY36:
51cf4a
            ddir = str(ddir)
51cf4a
        dfile = os.path.join(ddir, name)
51cf4a
51cf4a
    if stripdir is not None:
51cf4a
        fullname_parts = fullname.split(os.path.sep)
51cf4a
        stripdir_parts = stripdir.split(os.path.sep)
51cf4a
        ddir_parts = list(fullname_parts)
51cf4a
51cf4a
        for spart, opart in zip(stripdir_parts, fullname_parts):
51cf4a
            if spart == opart:
51cf4a
                ddir_parts.remove(spart)
51cf4a
51cf4a
        dfile = os.path.join(*ddir_parts)
51cf4a
51cf4a
    if prependdir is not None:
51cf4a
        if dfile is None:
51cf4a
            dfile = os.path.join(prependdir, fullname)
51cf4a
        else:
51cf4a
            dfile = os.path.join(prependdir, dfile)
51cf4a
51cf4a
    if isinstance(optimize, int):
51cf4a
        optimize = [optimize]
51cf4a
51cf4a
        if hardlink_dupes:
51cf4a
            raise ValueError(("Hardlinking of duplicated bytecode makes sense "
51cf4a
                              "only for more than one optimization level."))
51cf4a
51cf4a
    if rx is not None:
51cf4a
        mo = rx.search(fullname)
51cf4a
        if mo:
51cf4a
            return success
51cf4a
51cf4a
    if limit_sl_dest is not None and os.path.islink(fullname):
51cf4a
        if Path(limit_sl_dest).resolve() not in Path(fullname).resolve().parents:
51cf4a
            return success
51cf4a
51cf4a
    opt_cfiles = {}
51cf4a
51cf4a
    if os.path.isfile(fullname):
51cf4a
        for opt_level in optimize:
51cf4a
            if legacy:
51cf4a
                opt_cfiles[opt_level] = fullname + 'c'
51cf4a
            else:
51cf4a
                if opt_level >= 0:
51cf4a
                    opt = opt_level if opt_level >= 1 else ''
51cf4a
                    opt_kwarg = optimization_kwarg(opt)
51cf4a
                    cfile = (importlib.util.cache_from_source(
51cf4a
                             fullname, **opt_kwarg))
51cf4a
                    opt_cfiles[opt_level] = cfile
51cf4a
                else:
51cf4a
                    cfile = importlib.util.cache_from_source(fullname)
51cf4a
                    opt_cfiles[opt_level] = cfile
51cf4a
51cf4a
        head, tail = name[:-3], name[-3:]
51cf4a
        if tail == '.py':
51cf4a
            if not force:
51cf4a
                try:
51cf4a
                    mtime = int(os.stat(fullname).st_mtime)
51cf4a
                    expect = struct.pack(*(pyc_header_format + (mtime,)))
51cf4a
                    for cfile in opt_cfiles.values():
51cf4a
                        with open(cfile, 'rb') as chandle:
51cf4a
                            actual = chandle.read(pyc_header_lenght)
51cf4a
                        if expect != actual:
51cf4a
                            break
51cf4a
                    else:
51cf4a
                        return success
51cf4a
                except OSError:
51cf4a
                    pass
51cf4a
            if not quiet:
51cf4a
                print('Compiling {!r}...'.format(fullname))
51cf4a
            try:
51cf4a
                for index, opt_level in enumerate(sorted(optimize)):
51cf4a
                    cfile = opt_cfiles[opt_level]
51cf4a
                    if PY37:
51cf4a
                        ok = py_compile.compile(fullname, cfile, dfile, True,
51cf4a
                                                optimize=opt_level,
51cf4a
                                                invalidation_mode=invalidation_mode)
51cf4a
                    else:
51cf4a
                        ok = py_compile.compile(fullname, cfile, dfile, True,
51cf4a
                                                optimize=opt_level)
51cf4a
51cf4a
                    if index > 0 and hardlink_dupes:
51cf4a
                        previous_cfile = opt_cfiles[optimize[index - 1]]
51cf4a
                        if previous_cfile == cfile and optimize[0] not in (1, 2):
51cf4a
                            # Python 3.4 has only one .pyo file for -O and -OO so
51cf4a
                            # we hardlink it only if there is a .pyc file
51cf4a
                            # with the same content
51cf4a
                            previous_cfile = opt_cfiles[optimize[0]]
51cf4a
                        if  previous_cfile != cfile and filecmp.cmp(cfile, previous_cfile, shallow=False):
51cf4a
                            os.unlink(cfile)
51cf4a
                            os.link(previous_cfile, cfile)
51cf4a
51cf4a
            except py_compile.PyCompileError as err:
51cf4a
                success = False
51cf4a
                if quiet >= 2:
51cf4a
                    return success
51cf4a
                elif quiet:
51cf4a
                    print('*** Error compiling {!r}...'.format(fullname))
51cf4a
                else:
51cf4a
                    print('*** ', end='')
51cf4a
                # escape non-printable characters in msg
51cf4a
                msg = err.msg.encode(sys.stdout.encoding,
51cf4a
                                     errors='backslashreplace')
51cf4a
                msg = msg.decode(sys.stdout.encoding)
51cf4a
                print(msg)
51cf4a
            except (SyntaxError, UnicodeError, OSError) as e:
51cf4a
                success = False
51cf4a
                if quiet >= 2:
51cf4a
                    return success
51cf4a
                elif quiet:
51cf4a
                    print('*** Error compiling {!r}...'.format(fullname))
51cf4a
                else:
51cf4a
                    print('*** ', end='')
51cf4a
                print(e.__class__.__name__ + ':', e)
51cf4a
            else:
51cf4a
                if ok == 0:
51cf4a
                    success = False
51cf4a
    return success
51cf4a
51cf4a
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
51cf4a
                 legacy=False, optimize=-1,
51cf4a
                 invalidation_mode=None):
51cf4a
    """Byte-compile all module on sys.path.
51cf4a
51cf4a
    Arguments (all optional):
51cf4a
51cf4a
    skip_curdir: if true, skip current directory (default True)
51cf4a
    maxlevels:   max recursion level (default 0)
51cf4a
    force: as for compile_dir() (default False)
51cf4a
    quiet: as for compile_dir() (default 0)
51cf4a
    legacy: as for compile_dir() (default False)
51cf4a
    optimize: as for compile_dir() (default -1)
51cf4a
    invalidation_mode: as for compiler_dir()
51cf4a
    """
51cf4a
    success = True
51cf4a
    for dir in sys.path:
51cf4a
        if (not dir or dir == os.curdir) and skip_curdir:
51cf4a
            if quiet < 2:
51cf4a
                print('Skipping current directory')
51cf4a
        else:
51cf4a
            success = success and compile_dir(
51cf4a
                dir,
51cf4a
                maxlevels,
51cf4a
                None,
51cf4a
                force,
51cf4a
                quiet=quiet,
51cf4a
                legacy=legacy,
51cf4a
                optimize=optimize,
51cf4a
                invalidation_mode=invalidation_mode,
51cf4a
            )
51cf4a
    return success
51cf4a
51cf4a
51cf4a
def main():
51cf4a
    """Script main program."""
51cf4a
    import argparse
51cf4a
51cf4a
    parser = argparse.ArgumentParser(
51cf4a
        description='Utilities to support installing Python libraries.')
51cf4a
    parser.add_argument('-l', action='store_const', const=0,
51cf4a
                        default=None, dest='maxlevels',
51cf4a
                        help="don't recurse into subdirectories")
51cf4a
    parser.add_argument('-r', type=int, dest='recursion',
51cf4a
                        help=('control the maximum recursion level. '
51cf4a
                              'if `-l` and `-r` options are specified, '
51cf4a
                              'then `-r` takes precedence.'))
51cf4a
    parser.add_argument('-f', action='store_true', dest='force',
51cf4a
                        help='force rebuild even if timestamps are up to date')
51cf4a
    parser.add_argument('-q', action='count', dest='quiet', default=0,
51cf4a
                        help='output only error messages; -qq will suppress '
51cf4a
                             'the error messages as well.')
51cf4a
    parser.add_argument('-b', action='store_true', dest='legacy',
51cf4a
                        help='use legacy (pre-PEP3147) compiled file locations')
51cf4a
    parser.add_argument('-d', metavar='DESTDIR',  dest='ddir', default=None,
51cf4a
                        help=('directory to prepend to file paths for use in '
51cf4a
                              'compile-time tracebacks and in runtime '
51cf4a
                              'tracebacks in cases where the source file is '
51cf4a
                              'unavailable'))
51cf4a
    parser.add_argument('-s', metavar='STRIPDIR',  dest='stripdir',
51cf4a
                        default=None,
51cf4a
                        help=('part of path to left-strip from path '
51cf4a
                              'to source file - for example buildroot. '
51cf4a
                              '`-d` and `-s` options cannot be '
51cf4a
                              'specified together.'))
51cf4a
    parser.add_argument('-p', metavar='PREPENDDIR',  dest='prependdir',
51cf4a
                        default=None,
51cf4a
                        help=('path to add as prefix to path '
51cf4a
                              'to source file - for example / to make '
51cf4a
                              'it absolute when some part is removed '
51cf4a
                              'by `-s` option. '
51cf4a
                              '`-d` and `-p` options cannot be '
51cf4a
                              'specified together.'))
51cf4a
    parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
51cf4a
                        help=('skip files matching the regular expression; '
51cf4a
                              'the regexp is searched for in the full path '
51cf4a
                              'of each file considered for compilation'))
51cf4a
    parser.add_argument('-i', metavar='FILE', dest='flist',
51cf4a
                        help=('add all the files and directories listed in '
51cf4a
                              'FILE to the list considered for compilation; '
51cf4a
                              'if "-", names are read from stdin'))
51cf4a
    parser.add_argument('compile_dest', metavar='FILE|DIR', nargs='*',
51cf4a
                        help=('zero or more file and directory names '
51cf4a
                              'to compile; if no arguments given, defaults '
51cf4a
                              'to the equivalent of -l sys.path'))
51cf4a
    parser.add_argument('-j', '--workers', default=1,
51cf4a
                        type=int, help='Run compileall concurrently')
51cf4a
    parser.add_argument('-o', action='append', type=int, dest='opt_levels',
51cf4a
                        help=('Optimization levels to run compilation with. '
51cf4a
                              'Default is -1 which uses optimization level of '
51cf4a
                              'Python interpreter itself (specified by -O).'))
51cf4a
    parser.add_argument('-e', metavar='DIR', dest='limit_sl_dest',
51cf4a
                        help='Ignore symlinks pointing outsite of the DIR')
51cf4a
    parser.add_argument('--hardlink-dupes', action='store_true',
51cf4a
                        dest='hardlink_dupes',
51cf4a
                        help='Hardlink duplicated pyc files')
51cf4a
51cf4a
    if PY37:
51cf4a
        invalidation_modes = [mode.name.lower().replace('_', '-')
51cf4a
                              for mode in py_compile.PycInvalidationMode]
51cf4a
        parser.add_argument('--invalidation-mode',
51cf4a
                            choices=sorted(invalidation_modes),
51cf4a
                            help=('set .pyc invalidation mode; defaults to '
51cf4a
                                  '"checked-hash" if the SOURCE_DATE_EPOCH '
51cf4a
                                  'environment variable is set, and '
51cf4a
                                  '"timestamp" otherwise.'))
51cf4a
51cf4a
    args = parser.parse_args()
51cf4a
    compile_dests = args.compile_dest
51cf4a
51cf4a
    if args.rx:
51cf4a
        import re
51cf4a
        args.rx = re.compile(args.rx)
51cf4a
51cf4a
    if args.limit_sl_dest == "":
51cf4a
        args.limit_sl_dest = None
51cf4a
51cf4a
    if args.recursion is not None:
51cf4a
        maxlevels = args.recursion
51cf4a
    else:
51cf4a
        maxlevels = args.maxlevels
51cf4a
51cf4a
    if args.opt_levels is None:
51cf4a
        args.opt_levels = [-1]
51cf4a
51cf4a
    if len(args.opt_levels) == 1 and args.hardlink_dupes:
51cf4a
        parser.error(("Hardlinking of duplicated bytecode makes sense "
51cf4a
                      "only for more than one optimization level."))
51cf4a
51cf4a
    if args.ddir is not None and (
51cf4a
        args.stripdir is not None or args.prependdir is not None
51cf4a
    ):
51cf4a
        parser.error("-d cannot be used in combination with -s or -p")
51cf4a
51cf4a
    # if flist is provided then load it
51cf4a
    if args.flist:
51cf4a
        try:
51cf4a
            with (sys.stdin if args.flist=='-' else open(args.flist)) as f:
51cf4a
                for line in f:
51cf4a
                    compile_dests.append(line.strip())
51cf4a
        except OSError:
51cf4a
            if args.quiet < 2:
51cf4a
                print("Error reading file list {}".format(args.flist))
51cf4a
            return False
51cf4a
51cf4a
    if args.workers is not None:
51cf4a
        args.workers = args.workers or None
51cf4a
51cf4a
    if PY37 and args.invalidation_mode:
51cf4a
        ivl_mode = args.invalidation_mode.replace('-', '_').upper()
51cf4a
        invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
51cf4a
    else:
51cf4a
        invalidation_mode = None
51cf4a
51cf4a
    success = True
51cf4a
    try:
51cf4a
        if compile_dests:
51cf4a
            for dest in compile_dests:
51cf4a
                if os.path.isfile(dest):
51cf4a
                    if not compile_file(dest, args.ddir, args.force, args.rx,
51cf4a
                                        args.quiet, args.legacy,
51cf4a
                                        invalidation_mode=invalidation_mode,
51cf4a
                                        stripdir=args.stripdir,
51cf4a
                                        prependdir=args.prependdir,
51cf4a
                                        optimize=args.opt_levels,
51cf4a
                                        limit_sl_dest=args.limit_sl_dest,
51cf4a
                                        hardlink_dupes=args.hardlink_dupes):
51cf4a
                        success = False
51cf4a
                else:
51cf4a
                    if not compile_dir(dest, maxlevels, args.ddir,
51cf4a
                                       args.force, args.rx, args.quiet,
51cf4a
                                       args.legacy, workers=args.workers,
51cf4a
                                       invalidation_mode=invalidation_mode,
51cf4a
                                       stripdir=args.stripdir,
51cf4a
                                       prependdir=args.prependdir,
51cf4a
                                       optimize=args.opt_levels,
51cf4a
                                       limit_sl_dest=args.limit_sl_dest,
51cf4a
                                       hardlink_dupes=args.hardlink_dupes):
51cf4a
                        success = False
51cf4a
            return success
51cf4a
        else:
51cf4a
            return compile_path(legacy=args.legacy, force=args.force,
51cf4a
                                quiet=args.quiet,
51cf4a
                                invalidation_mode=invalidation_mode)
51cf4a
    except KeyboardInterrupt:
51cf4a
        if args.quiet < 2:
51cf4a
            print("\n[interrupted]")
51cf4a
        return False
51cf4a
    return True
51cf4a
51cf4a
51cf4a
if __name__ == '__main__':
51cf4a
    exit_status = int(not main())
51cf4a
    sys.exit(exit_status)