Blame SOURCES/compileall2.py

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