codekingpro/portable-devtools
114k
1"""distutils.command.install_lib2 3Implements the Distutils 'install_lib' command4(install all Python modules)."""5 6import importlib.util7import os8import sys9 10from ..core import Command11from ..errors import DistutilsOptionError12 13# Extension for Python source files.14PYTHON_SOURCE_EXTENSION = ".py"15 16 17class install_lib(Command):18 description = "install all Python modules (extensions and pure Python)"19 20 # The byte-compilation options are a tad confusing. Here are the21 # possible scenarios:22 # 1) no compilation at all (--no-compile --no-optimize)23 # 2) compile .pyc only (--compile --no-optimize; default)24 # 3) compile .pyc and "opt-1" .pyc (--compile --optimize)25 # 4) compile "opt-1" .pyc only (--no-compile --optimize)26 # 5) compile .pyc and "opt-2" .pyc (--compile --optimize-more)27 # 6) compile "opt-2" .pyc only (--no-compile --optimize-more)28 #29 # The UI for this is two options, 'compile' and 'optimize'.30 # 'compile' is strictly boolean, and only decides whether to31 # generate .pyc files. 'optimize' is three-way (0, 1, or 2), and32 # decides both whether to generate .pyc files and what level of33 # optimization to use.34 35 user_options = [36 ('install-dir=', 'd', "directory to install to"),37 ('build-dir=', 'b', "build directory (where to install from)"),38 ('force', 'f', "force installation (overwrite existing files)"),39 ('compile', 'c', "compile .py to .pyc [default]"),40 ('no-compile', None, "don't compile .py files"),41 (42 'optimize=',43 'O',44 "also compile with optimization: -O1 for \"python -O\", "45 "-O2 for \"python -OO\", and -O0 to disable [default: -O0]",46 ),47 ('skip-build', None, "skip the build steps"),48 ]49 50 boolean_options = ['force', 'compile', 'skip-build']51 negative_opt = {'no-compile': 'compile'}52 53 def initialize_options(self):54 # let the 'install' command dictate our installation directory55 self.install_dir = None56 self.build_dir = None57 self.force = 058 self.compile = None59 self.optimize = None60 self.skip_build = None61 62 def finalize_options(self):63 # Get all the information we need to install pure Python modules64 # from the umbrella 'install' command -- build (source) directory,65 # install (target) directory, and whether to compile .py files.66 self.set_undefined_options(67 'install',68 ('build_lib', 'build_dir'),69 ('install_lib', 'install_dir'),70 ('force', 'force'),71 ('compile', 'compile'),72 ('optimize', 'optimize'),73 ('skip_build', 'skip_build'),74 )75 76 if self.compile is None:77 self.compile = True78 if self.optimize is None:79 self.optimize = False80 81 if not isinstance(self.optimize, int):82 try:83 self.optimize = int(self.optimize)84 if self.optimize not in (0, 1, 2):85 raise AssertionError86 except (ValueError, AssertionError):87 raise DistutilsOptionError("optimize must be 0, 1, or 2")88 89 def run(self):90 # Make sure we have built everything we need first91 self.build()92 93 # Install everything: simply dump the entire contents of the build94 # directory to the installation directory (that's the beauty of95 # having a build directory!)96 outfiles = self.install()97 98 # (Optionally) compile .py to .pyc99 if outfiles is not None and self.distribution.has_pure_modules():100 self.byte_compile(outfiles)101 102 # -- Top-level worker functions ------------------------------------103 # (called from 'run()')104 105 def build(self):106 if not self.skip_build:107 if self.distribution.has_pure_modules():108 self.run_command('build_py')109 if self.distribution.has_ext_modules():110 self.run_command('build_ext')111 112 def install(self):113 if os.path.isdir(self.build_dir):114 outfiles = self.copy_tree(self.build_dir, self.install_dir)115 else:116 self.warn(117 "'%s' does not exist -- no Python modules to install" % self.build_dir118 )119 return120 return outfiles121 122 def byte_compile(self, files):123 if sys.dont_write_bytecode:124 self.warn('byte-compiling is disabled, skipping.')125 return126 127 from ..util import byte_compile128 129 # Get the "--root" directory supplied to the "install" command,130 # and use it as a prefix to strip off the purported filename131 # encoded in bytecode files. This is far from complete, but it132 # should at least generate usable bytecode in RPM distributions.133 install_root = self.get_finalized_command('install').root134 135 if self.compile:136 byte_compile(137 files,138 optimize=0,139 force=self.force,140 prefix=install_root,141 dry_run=self.dry_run,142 )143 if self.optimize > 0:144 byte_compile(145 files,146 optimize=self.optimize,147 force=self.force,148 prefix=install_root,149 verbose=self.verbose,150 dry_run=self.dry_run,151 )152 153 # -- Utility methods -----------------------------------------------154 155 def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir):156 if not has_any:157 return []158 159 build_cmd = self.get_finalized_command(build_cmd)160 build_files = build_cmd.get_outputs()161 build_dir = getattr(build_cmd, cmd_option)162 163 prefix_len = len(build_dir) + len(os.sep)164 outputs = []165 for file in build_files:166 outputs.append(os.path.join(output_dir, file[prefix_len:]))167 168 return outputs169 170 def _bytecode_filenames(self, py_filenames):171 bytecode_files = []172 for py_file in py_filenames:173 # Since build_py handles package data installation, the174 # list of outputs can contain more than just .py files.175 # Make sure we only report bytecode for the .py files.176 ext = os.path.splitext(os.path.normcase(py_file))[1]177 if ext != PYTHON_SOURCE_EXTENSION:178 continue179 if self.compile:180 bytecode_files.append(181 importlib.util.cache_from_source(py_file, optimization='')182 )183 if self.optimize > 0:184 bytecode_files.append(185 importlib.util.cache_from_source(186 py_file, optimization=self.optimize187 )188 )189 190 return bytecode_files191 192 # -- External interface --------------------------------------------193 # (called by outsiders)194 195 def get_outputs(self):196 """Return the list of files that would be installed if this command197 were actually run. Not affected by the "dry-run" flag or whether198 modules have actually been built yet.199 """200 pure_outputs = self._mutate_outputs(201 self.distribution.has_pure_modules(),202 'build_py',203 'build_lib',204 self.install_dir,205 )206 if self.compile:207 bytecode_outputs = self._bytecode_filenames(pure_outputs)208 else:209 bytecode_outputs = []210 211 ext_outputs = self._mutate_outputs(212 self.distribution.has_ext_modules(),213 'build_ext',214 'build_lib',215 self.install_dir,216 )217 218 return pure_outputs + bytecode_outputs + ext_outputs219 220 def get_inputs(self):221 """Get the list of files that are input to this command, ie. the222 files that get installed as they are named in the build tree.223 The files in this list correspond one-to-one to the output224 filenames returned by 'get_outputs()'.225 """226 inputs = []227 228 if self.distribution.has_pure_modules():229 build_py = self.get_finalized_command('build_py')230 inputs.extend(build_py.get_outputs())231 232 if self.distribution.has_ext_modules():233 build_ext = self.get_finalized_command('build_ext')234 inputs.extend(build_ext.get_outputs())235 236 return inputs237 