(root)/
Python-3.12.0/
Lib/
compileall.py
       1  """Module/script to byte-compile all .py files to .pyc files.
       2  
       3  When called as a script with arguments, this compiles the directories
       4  given as arguments recursively; the -l option prevents it from
       5  recursing into directories.
       6  
       7  Without arguments, it compiles all modules on sys.path, without
       8  recursing into subdirectories.  (Even though it should do so for
       9  packages -- for now, you'll have to deal with packages separately.)
      10  
      11  See module py_compile for details of the actual byte-compilation.
      12  """
      13  import os
      14  import sys
      15  import importlib.util
      16  import py_compile
      17  import struct
      18  import filecmp
      19  
      20  from functools import partial
      21  from pathlib import Path
      22  
      23  __all__ = ["compile_dir","compile_file","compile_path"]
      24  
      25  def _walk_dir(dir, maxlevels, quiet=0):
      26      if quiet < 2 and isinstance(dir, os.PathLike):
      27          dir = os.fspath(dir)
      28      if not quiet:
      29          print('Listing {!r}...'.format(dir))
      30      try:
      31          names = os.listdir(dir)
      32      except OSError:
      33          if quiet < 2:
      34              print("Can't list {!r}".format(dir))
      35          names = []
      36      names.sort()
      37      for name in names:
      38          if name == '__pycache__':
      39              continue
      40          fullname = os.path.join(dir, name)
      41          if not os.path.isdir(fullname):
      42              yield fullname
      43          elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
      44                os.path.isdir(fullname) and not os.path.islink(fullname)):
      45              yield from _walk_dir(fullname, maxlevels=maxlevels - 1,
      46                                   quiet=quiet)
      47  
      48  def compile_dir(dir, maxlevels=None, ddir=None, force=False,
      49                  rx=None, quiet=0, legacy=False, optimize=-1, workers=1,
      50                  invalidation_mode=None, *, stripdir=None,
      51                  prependdir=None, limit_sl_dest=None, hardlink_dupes=False):
      52      """Byte-compile all modules in the given directory tree.
      53  
      54      Arguments (only dir is required):
      55  
      56      dir:       the directory to byte-compile
      57      maxlevels: maximum recursion level (default `sys.getrecursionlimit()`)
      58      ddir:      the directory that will be prepended to the path to the
      59                 file as it is compiled into each byte-code file.
      60      force:     if True, force compilation, even if timestamps are up-to-date
      61      quiet:     full output with False or 0, errors only with 1,
      62                 no output with 2
      63      legacy:    if True, produce legacy pyc paths instead of PEP 3147 paths
      64      optimize:  int or list of optimization levels or -1 for level of
      65                 the interpreter. Multiple levels leads to multiple compiled
      66                 files each with one optimization level.
      67      workers:   maximum number of parallel workers
      68      invalidation_mode: how the up-to-dateness of the pyc will be checked
      69      stripdir:  part of path to left-strip from source file path
      70      prependdir: path to prepend to beginning of original file path, applied
      71                 after stripdir
      72      limit_sl_dest: ignore symlinks if they are pointing outside of
      73                     the defined path
      74      hardlink_dupes: hardlink duplicated pyc files
      75      """
      76      ProcessPoolExecutor = None
      77      if ddir is not None and (stripdir is not None or prependdir is not None):
      78          raise ValueError(("Destination dir (ddir) cannot be used "
      79                            "in combination with stripdir or prependdir"))
      80      if ddir is not None:
      81          stripdir = dir
      82          prependdir = ddir
      83          ddir = None
      84      if workers < 0:
      85          raise ValueError('workers must be greater or equal to 0')
      86      if workers != 1:
      87          # Check if this is a system where ProcessPoolExecutor can function.
      88          from concurrent.futures.process import _check_system_limits
      89          try:
      90              _check_system_limits()
      91          except NotImplementedError:
      92              workers = 1
      93          else:
      94              from concurrent.futures import ProcessPoolExecutor
      95      if maxlevels is None:
      96          maxlevels = sys.getrecursionlimit()
      97      files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels)
      98      success = True
      99      if workers != 1 and ProcessPoolExecutor is not None:
     100          import multiprocessing
     101          if multiprocessing.get_start_method() == 'fork':
     102              mp_context = multiprocessing.get_context('forkserver')
     103          else:
     104              mp_context = None
     105          # If workers == 0, let ProcessPoolExecutor choose
     106          workers = workers or None
     107          with ProcessPoolExecutor(max_workers=workers,
     108                                   mp_context=mp_context) as executor:
     109              results = executor.map(partial(compile_file,
     110                                             ddir=ddir, force=force,
     111                                             rx=rx, quiet=quiet,
     112                                             legacy=legacy,
     113                                             optimize=optimize,
     114                                             invalidation_mode=invalidation_mode,
     115                                             stripdir=stripdir,
     116                                             prependdir=prependdir,
     117                                             limit_sl_dest=limit_sl_dest,
     118                                             hardlink_dupes=hardlink_dupes),
     119                                     files)
     120              success = min(results, default=True)
     121      else:
     122          for file in files:
     123              if not compile_file(file, ddir, force, rx, quiet,
     124                                  legacy, optimize, invalidation_mode,
     125                                  stripdir=stripdir, prependdir=prependdir,
     126                                  limit_sl_dest=limit_sl_dest,
     127                                  hardlink_dupes=hardlink_dupes):
     128                  success = False
     129      return success
     130  
     131  def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
     132                   legacy=False, optimize=-1,
     133                   invalidation_mode=None, *, stripdir=None, prependdir=None,
     134                   limit_sl_dest=None, hardlink_dupes=False):
     135      """Byte-compile one file.
     136  
     137      Arguments (only fullname is required):
     138  
     139      fullname:  the file to byte-compile
     140      ddir:      if given, the directory name compiled in to the
     141                 byte-code file.
     142      force:     if True, force compilation, even if timestamps are up-to-date
     143      quiet:     full output with False or 0, errors only with 1,
     144                 no output with 2
     145      legacy:    if True, produce legacy pyc paths instead of PEP 3147 paths
     146      optimize:  int or list of optimization levels or -1 for level of
     147                 the interpreter. Multiple levels leads to multiple compiled
     148                 files each with one optimization level.
     149      invalidation_mode: how the up-to-dateness of the pyc will be checked
     150      stripdir:  part of path to left-strip from source file path
     151      prependdir: path to prepend to beginning of original file path, applied
     152                 after stripdir
     153      limit_sl_dest: ignore symlinks if they are pointing outside of
     154                     the defined path.
     155      hardlink_dupes: hardlink duplicated pyc files
     156      """
     157  
     158      if ddir is not None and (stripdir is not None or prependdir is not None):
     159          raise ValueError(("Destination dir (ddir) cannot be used "
     160                            "in combination with stripdir or prependdir"))
     161  
     162      success = True
     163      fullname = os.fspath(fullname)
     164      stripdir = os.fspath(stripdir) if stripdir is not None else None
     165      name = os.path.basename(fullname)
     166  
     167      dfile = None
     168  
     169      if ddir is not None:
     170          dfile = os.path.join(ddir, name)
     171  
     172      if stripdir is not None:
     173          fullname_parts = fullname.split(os.path.sep)
     174          stripdir_parts = stripdir.split(os.path.sep)
     175          ddir_parts = list(fullname_parts)
     176  
     177          for spart, opart in zip(stripdir_parts, fullname_parts):
     178              if spart == opart:
     179                  ddir_parts.remove(spart)
     180  
     181          dfile = os.path.join(*ddir_parts)
     182  
     183      if prependdir is not None:
     184          if dfile is None:
     185              dfile = os.path.join(prependdir, fullname)
     186          else:
     187              dfile = os.path.join(prependdir, dfile)
     188  
     189      if isinstance(optimize, int):
     190          optimize = [optimize]
     191  
     192      # Use set() to remove duplicates.
     193      # Use sorted() to create pyc files in a deterministic order.
     194      optimize = sorted(set(optimize))
     195  
     196      if hardlink_dupes and len(optimize) < 2:
     197          raise ValueError("Hardlinking of duplicated bytecode makes sense "
     198                            "only for more than one optimization level")
     199  
     200      if rx is not None:
     201          mo = rx.search(fullname)
     202          if mo:
     203              return success
     204  
     205      if limit_sl_dest is not None and os.path.islink(fullname):
     206          if Path(limit_sl_dest).resolve() not in Path(fullname).resolve().parents:
     207              return success
     208  
     209      opt_cfiles = {}
     210  
     211      if os.path.isfile(fullname):
     212          for opt_level in optimize:
     213              if legacy:
     214                  opt_cfiles[opt_level] = fullname + 'c'
     215              else:
     216                  if opt_level >= 0:
     217                      opt = opt_level if opt_level >= 1 else ''
     218                      cfile = (importlib.util.cache_from_source(
     219                               fullname, optimization=opt))
     220                      opt_cfiles[opt_level] = cfile
     221                  else:
     222                      cfile = importlib.util.cache_from_source(fullname)
     223                      opt_cfiles[opt_level] = cfile
     224  
     225          head, tail = name[:-3], name[-3:]
     226          if tail == '.py':
     227              if not force:
     228                  try:
     229                      mtime = int(os.stat(fullname).st_mtime)
     230                      expect = struct.pack('<4sLL', importlib.util.MAGIC_NUMBER,
     231                                           0, mtime & 0xFFFF_FFFF)
     232                      for cfile in opt_cfiles.values():
     233                          with open(cfile, 'rb') as chandle:
     234                              actual = chandle.read(12)
     235                          if expect != actual:
     236                              break
     237                      else:
     238                          return success
     239                  except OSError:
     240                      pass
     241              if not quiet:
     242                  print('Compiling {!r}...'.format(fullname))
     243              try:
     244                  for index, opt_level in enumerate(optimize):
     245                      cfile = opt_cfiles[opt_level]
     246                      ok = py_compile.compile(fullname, cfile, dfile, True,
     247                                              optimize=opt_level,
     248                                              invalidation_mode=invalidation_mode)
     249                      if index > 0 and hardlink_dupes:
     250                          previous_cfile = opt_cfiles[optimize[index - 1]]
     251                          if filecmp.cmp(cfile, previous_cfile, shallow=False):
     252                              os.unlink(cfile)
     253                              os.link(previous_cfile, cfile)
     254              except py_compile.PyCompileError as err:
     255                  success = False
     256                  if quiet >= 2:
     257                      return success
     258                  elif quiet:
     259                      print('*** Error compiling {!r}...'.format(fullname))
     260                  else:
     261                      print('*** ', end='')
     262                  # escape non-printable characters in msg
     263                  encoding = sys.stdout.encoding or sys.getdefaultencoding()
     264                  msg = err.msg.encode(encoding, errors='backslashreplace').decode(encoding)
     265                  print(msg)
     266              except (SyntaxError, UnicodeError, OSError) as e:
     267                  success = False
     268                  if quiet >= 2:
     269                      return success
     270                  elif quiet:
     271                      print('*** Error compiling {!r}...'.format(fullname))
     272                  else:
     273                      print('*** ', end='')
     274                  print(e.__class__.__name__ + ':', e)
     275              else:
     276                  if ok == 0:
     277                      success = False
     278      return success
     279  
     280  def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
     281                   legacy=False, optimize=-1,
     282                   invalidation_mode=None):
     283      """Byte-compile all module on sys.path.
     284  
     285      Arguments (all optional):
     286  
     287      skip_curdir: if true, skip current directory (default True)
     288      maxlevels:   max recursion level (default 0)
     289      force: as for compile_dir() (default False)
     290      quiet: as for compile_dir() (default 0)
     291      legacy: as for compile_dir() (default False)
     292      optimize: as for compile_dir() (default -1)
     293      invalidation_mode: as for compiler_dir()
     294      """
     295      success = True
     296      for dir in sys.path:
     297          if (not dir or dir == os.curdir) and skip_curdir:
     298              if quiet < 2:
     299                  print('Skipping current directory')
     300          else:
     301              success = success and compile_dir(
     302                  dir,
     303                  maxlevels,
     304                  None,
     305                  force,
     306                  quiet=quiet,
     307                  legacy=legacy,
     308                  optimize=optimize,
     309                  invalidation_mode=invalidation_mode,
     310              )
     311      return success
     312  
     313  
     314  def main():
     315      """Script main program."""
     316      import argparse
     317  
     318      parser = argparse.ArgumentParser(
     319          description='Utilities to support installing Python libraries.')
     320      parser.add_argument('-l', action='store_const', const=0,
     321                          default=None, dest='maxlevels',
     322                          help="don't recurse into subdirectories")
     323      parser.add_argument('-r', type=int, dest='recursion',
     324                          help=('control the maximum recursion level. '
     325                                'if `-l` and `-r` options are specified, '
     326                                'then `-r` takes precedence.'))
     327      parser.add_argument('-f', action='store_true', dest='force',
     328                          help='force rebuild even if timestamps are up to date')
     329      parser.add_argument('-q', action='count', dest='quiet', default=0,
     330                          help='output only error messages; -qq will suppress '
     331                               'the error messages as well.')
     332      parser.add_argument('-b', action='store_true', dest='legacy',
     333                          help='use legacy (pre-PEP3147) compiled file locations')
     334      parser.add_argument('-d', metavar='DESTDIR',  dest='ddir', default=None,
     335                          help=('directory to prepend to file paths for use in '
     336                                'compile-time tracebacks and in runtime '
     337                                'tracebacks in cases where the source file is '
     338                                'unavailable'))
     339      parser.add_argument('-s', metavar='STRIPDIR',  dest='stripdir',
     340                          default=None,
     341                          help=('part of path to left-strip from path '
     342                                'to source file - for example buildroot. '
     343                                '`-d` and `-s` options cannot be '
     344                                'specified together.'))
     345      parser.add_argument('-p', metavar='PREPENDDIR',  dest='prependdir',
     346                          default=None,
     347                          help=('path to add as prefix to path '
     348                                'to source file - for example / to make '
     349                                'it absolute when some part is removed '
     350                                'by `-s` option. '
     351                                '`-d` and `-p` options cannot be '
     352                                'specified together.'))
     353      parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
     354                          help=('skip files matching the regular expression; '
     355                                'the regexp is searched for in the full path '
     356                                'of each file considered for compilation'))
     357      parser.add_argument('-i', metavar='FILE', dest='flist',
     358                          help=('add all the files and directories listed in '
     359                                'FILE to the list considered for compilation; '
     360                                'if "-", names are read from stdin'))
     361      parser.add_argument('compile_dest', metavar='FILE|DIR', nargs='*',
     362                          help=('zero or more file and directory names '
     363                                'to compile; if no arguments given, defaults '
     364                                'to the equivalent of -l sys.path'))
     365      parser.add_argument('-j', '--workers', default=1,
     366                          type=int, help='Run compileall concurrently')
     367      invalidation_modes = [mode.name.lower().replace('_', '-')
     368                            for mode in py_compile.PycInvalidationMode]
     369      parser.add_argument('--invalidation-mode',
     370                          choices=sorted(invalidation_modes),
     371                          help=('set .pyc invalidation mode; defaults to '
     372                                '"checked-hash" if the SOURCE_DATE_EPOCH '
     373                                'environment variable is set, and '
     374                                '"timestamp" otherwise.'))
     375      parser.add_argument('-o', action='append', type=int, dest='opt_levels',
     376                          help=('Optimization levels to run compilation with. '
     377                                'Default is -1 which uses the optimization level '
     378                                'of the Python interpreter itself (see -O).'))
     379      parser.add_argument('-e', metavar='DIR', dest='limit_sl_dest',
     380                          help='Ignore symlinks pointing outsite of the DIR')
     381      parser.add_argument('--hardlink-dupes', action='store_true',
     382                          dest='hardlink_dupes',
     383                          help='Hardlink duplicated pyc files')
     384  
     385      args = parser.parse_args()
     386      compile_dests = args.compile_dest
     387  
     388      if args.rx:
     389          import re
     390          args.rx = re.compile(args.rx)
     391  
     392      if args.limit_sl_dest == "":
     393          args.limit_sl_dest = None
     394  
     395      if args.recursion is not None:
     396          maxlevels = args.recursion
     397      else:
     398          maxlevels = args.maxlevels
     399  
     400      if args.opt_levels is None:
     401          args.opt_levels = [-1]
     402  
     403      if len(args.opt_levels) == 1 and args.hardlink_dupes:
     404          parser.error(("Hardlinking of duplicated bytecode makes sense "
     405                        "only for more than one optimization level."))
     406  
     407      if args.ddir is not None and (
     408          args.stripdir is not None or args.prependdir is not None
     409      ):
     410          parser.error("-d cannot be used in combination with -s or -p")
     411  
     412      # if flist is provided then load it
     413      if args.flist:
     414          try:
     415              with (sys.stdin if args.flist=='-' else
     416                      open(args.flist, encoding="utf-8")) as f:
     417                  for line in f:
     418                      compile_dests.append(line.strip())
     419          except OSError:
     420              if args.quiet < 2:
     421                  print("Error reading file list {}".format(args.flist))
     422              return False
     423  
     424      if args.invalidation_mode:
     425          ivl_mode = args.invalidation_mode.replace('-', '_').upper()
     426          invalidation_mode = py_compile.PycInvalidationMode[ivl_mode]
     427      else:
     428          invalidation_mode = None
     429  
     430      success = True
     431      try:
     432          if compile_dests:
     433              for dest in compile_dests:
     434                  if os.path.isfile(dest):
     435                      if not compile_file(dest, args.ddir, args.force, args.rx,
     436                                          args.quiet, args.legacy,
     437                                          invalidation_mode=invalidation_mode,
     438                                          stripdir=args.stripdir,
     439                                          prependdir=args.prependdir,
     440                                          optimize=args.opt_levels,
     441                                          limit_sl_dest=args.limit_sl_dest,
     442                                          hardlink_dupes=args.hardlink_dupes):
     443                          success = False
     444                  else:
     445                      if not compile_dir(dest, maxlevels, args.ddir,
     446                                         args.force, args.rx, args.quiet,
     447                                         args.legacy, workers=args.workers,
     448                                         invalidation_mode=invalidation_mode,
     449                                         stripdir=args.stripdir,
     450                                         prependdir=args.prependdir,
     451                                         optimize=args.opt_levels,
     452                                         limit_sl_dest=args.limit_sl_dest,
     453                                         hardlink_dupes=args.hardlink_dupes):
     454                          success = False
     455              return success
     456          else:
     457              return compile_path(legacy=args.legacy, force=args.force,
     458                                  quiet=args.quiet,
     459                                  invalidation_mode=invalidation_mode)
     460      except KeyboardInterrupt:
     461          if args.quiet < 2:
     462              print("\n[interrupted]")
     463          return False
     464      return True
     465  
     466  
     467  if __name__ == '__main__':
     468      exit_status = int(not main())
     469      sys.exit(exit_status)