Home | History | Annotate | Download | only in command
      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 install_lib(Command):
     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
    218