python (3.11.7)
       1  """distutils.command.install_lib
       2  
       3  Implements the Distutils 'install_lib' command
       4  (install all Python modules)."""
       5  
       6  import os
       7  import importlib.util
       8  import sys
       9  
      10  from distutils.core import Command
      11  from distutils.errors import DistutilsOptionError
      12  
      13  
      14  # Extension for Python source files.
      15  PYTHON_SOURCE_EXTENSION = ".py"
      16  
      17  class ESC[4;38;5;81minstall_lib(ESC[4;38;5;149mCommand):
      18  
      19      description = "install all Python modules (extensions and pure Python)"
      20  
      21      # The byte-compilation options are a tad confusing.  Here are the
      22      # possible scenarios:
      23      #   1) no compilation at all (--no-compile --no-optimize)
      24      #   2) compile .pyc only (--compile --no-optimize; default)
      25      #   3) compile .pyc and "opt-1" .pyc (--compile --optimize)
      26      #   4) compile "opt-1" .pyc only (--no-compile --optimize)
      27      #   5) compile .pyc and "opt-2" .pyc (--compile --optimize-more)
      28      #   6) compile "opt-2" .pyc only (--no-compile --optimize-more)
      29      #
      30      # The UI for this is two options, 'compile' and 'optimize'.
      31      # 'compile' is strictly boolean, and only decides whether to
      32      # generate .pyc files.  'optimize' is three-way (0, 1, or 2), and
      33      # decides both whether to generate .pyc files and what level of
      34      # optimization to use.
      35  
      36      user_options = [
      37          ('install-dir=', 'd', "directory to install to"),
      38          ('build-dir=','b', "build directory (where to install from)"),
      39          ('force', 'f', "force installation (overwrite existing files)"),
      40          ('compile', 'c', "compile .py to .pyc [default]"),
      41          ('no-compile', None, "don't compile .py files"),
      42          ('optimize=', 'O',
      43           "also compile with optimization: -O1 for \"python -O\", "
      44           "-O2 for \"python -OO\", and -O0 to disable [default: -O0]"),
      45          ('skip-build', None, "skip the build steps"),
      46          ]
      47  
      48      boolean_options = ['force', 'compile', 'skip-build']
      49      negative_opt = {'no-compile' : 'compile'}
      50  
      51      def initialize_options(self):
      52          # let the 'install' command dictate our installation directory
      53          self.install_dir = None
      54          self.build_dir = None
      55          self.force = 0
      56          self.compile = None
      57          self.optimize = None
      58          self.skip_build = None
      59  
      60      def finalize_options(self):
      61          # Get all the information we need to install pure Python modules
      62          # from the umbrella 'install' command -- build (source) directory,
      63          # install (target) directory, and whether to compile .py files.
      64          self.set_undefined_options('install',
      65                                     ('build_lib', 'build_dir'),
      66                                     ('install_lib', 'install_dir'),
      67                                     ('force', 'force'),
      68                                     ('compile', 'compile'),
      69                                     ('optimize', 'optimize'),
      70                                     ('skip_build', 'skip_build'),
      71                                    )
      72  
      73          if self.compile is None:
      74              self.compile = True
      75          if self.optimize is None:
      76              self.optimize = False
      77  
      78          if not isinstance(self.optimize, int):
      79              try:
      80                  self.optimize = int(self.optimize)
      81                  if self.optimize not in (0, 1, 2):
      82                      raise AssertionError
      83              except (ValueError, AssertionError):
      84                  raise DistutilsOptionError("optimize must be 0, 1, or 2")
      85  
      86      def run(self):
      87          # Make sure we have built everything we need first
      88          self.build()
      89  
      90          # Install everything: simply dump the entire contents of the build
      91          # directory to the installation directory (that's the beauty of
      92          # having a build directory!)
      93          outfiles = self.install()
      94  
      95          # (Optionally) compile .py to .pyc
      96          if outfiles is not None and self.distribution.has_pure_modules():
      97              self.byte_compile(outfiles)
      98  
      99      # -- Top-level worker functions ------------------------------------
     100      # (called from 'run()')
     101  
     102      def build(self):
     103          if not self.skip_build:
     104              if self.distribution.has_pure_modules():
     105                  self.run_command('build_py')
     106              if self.distribution.has_ext_modules():
     107                  self.run_command('build_ext')
     108  
     109      def install(self):
     110          if os.path.isdir(self.build_dir):
     111              outfiles = self.copy_tree(self.build_dir, self.install_dir)
     112          else:
     113              self.warn("'%s' does not exist -- no Python modules to install" %
     114                        self.build_dir)
     115              return
     116          return outfiles
     117  
     118      def byte_compile(self, files):
     119          if sys.dont_write_bytecode:
     120              self.warn('byte-compiling is disabled, skipping.')
     121              return
     122  
     123          from distutils.util import byte_compile
     124  
     125          # Get the "--root" directory supplied to the "install" command,
     126          # and use it as a prefix to strip off the purported filename
     127          # encoded in bytecode files.  This is far from complete, but it
     128          # should at least generate usable bytecode in RPM distributions.
     129          install_root = self.get_finalized_command('install').root
     130  
     131          if self.compile:
     132              byte_compile(files, optimize=0,
     133                           force=self.force, prefix=install_root,
     134                           dry_run=self.dry_run)
     135          if self.optimize > 0:
     136              byte_compile(files, optimize=self.optimize,
     137                           force=self.force, prefix=install_root,
     138                           verbose=self.verbose, dry_run=self.dry_run)
     139  
     140  
     141      # -- Utility methods -----------------------------------------------
     142  
     143      def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir):
     144          if not has_any:
     145              return []
     146  
     147          build_cmd = self.get_finalized_command(build_cmd)
     148          build_files = build_cmd.get_outputs()
     149          build_dir = getattr(build_cmd, cmd_option)
     150  
     151          prefix_len = len(build_dir) + len(os.sep)
     152          outputs = []
     153          for file in build_files:
     154              outputs.append(os.path.join(output_dir, file[prefix_len:]))
     155  
     156          return outputs
     157  
     158      def _bytecode_filenames(self, py_filenames):
     159          bytecode_files = []
     160          for py_file in py_filenames:
     161              # Since build_py handles package data installation, the
     162              # list of outputs can contain more than just .py files.
     163              # Make sure we only report bytecode for the .py files.
     164              ext = os.path.splitext(os.path.normcase(py_file))[1]
     165              if ext != PYTHON_SOURCE_EXTENSION:
     166                  continue
     167              if self.compile:
     168                  bytecode_files.append(importlib.util.cache_from_source(
     169                      py_file, optimization=''))
     170              if self.optimize > 0:
     171                  bytecode_files.append(importlib.util.cache_from_source(
     172                      py_file, optimization=self.optimize))
     173  
     174          return bytecode_files
     175  
     176  
     177      # -- External interface --------------------------------------------
     178      # (called by outsiders)
     179  
     180      def get_outputs(self):
     181          """Return the list of files that would be installed if this command
     182          were actually run.  Not affected by the "dry-run" flag or whether
     183          modules have actually been built yet.
     184          """
     185          pure_outputs = \
     186              self._mutate_outputs(self.distribution.has_pure_modules(),
     187                                   'build_py', 'build_lib',
     188                                   self.install_dir)
     189          if self.compile:
     190              bytecode_outputs = self._bytecode_filenames(pure_outputs)
     191          else:
     192              bytecode_outputs = []
     193  
     194          ext_outputs = \
     195              self._mutate_outputs(self.distribution.has_ext_modules(),
     196                                   'build_ext', 'build_lib',
     197                                   self.install_dir)
     198  
     199          return pure_outputs + bytecode_outputs + ext_outputs
     200  
     201      def get_inputs(self):
     202          """Get the list of files that are input to this command, ie. the
     203          files that get installed as they are named in the build tree.
     204          The files in this list correspond one-to-one to the output
     205          filenames returned by 'get_outputs()'.
     206          """
     207          inputs = []
     208  
     209          if self.distribution.has_pure_modules():
     210              build_py = self.get_finalized_command('build_py')
     211              inputs.extend(build_py.get_outputs())
     212  
     213          if self.distribution.has_ext_modules():
     214              build_ext = self.get_finalized_command('build_ext')
     215              inputs.extend(build_ext.get_outputs())
     216  
     217          return inputs