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