Aluode/PerceptionLabPortable
0
1"""distutils.command.install_lib2 3Implements the Distutils 'install_lib' command4(install all Python modules)."""5 6from __future__ import annotations7 8import importlib.util9import os10import sys11from typing import Any, ClassVar12 13from ..core import Command14from ..errors import DistutilsOptionError15 16# Extension for Python source files.17PYTHON_SOURCE_EXTENSION = ".py"18 19 20class install_lib(Command):21 description = "install all Python modules (extensions and pure Python)"22 23 # The byte-compilation options are a tad confusing. Here are the24 # possible scenarios:25 # 1) no compilation at all (--no-compile --no-optimize)26 # 2) compile .pyc only (--compile --no-optimize; default)27 # 3) compile .pyc and "opt-1" .pyc (--compile --optimize)28 # 4) compile "opt-1" .pyc only (--no-compile --optimize)29 # 5) compile .pyc and "opt-2" .pyc (--compile --optimize-more)30 # 6) compile "opt-2" .pyc only (--no-compile --optimize-more)31 #32 # The UI for this is two options, 'compile' and 'optimize'.33 # 'compile' is strictly boolean, and only decides whether to34 # generate .pyc files. 'optimize' is three-way (0, 1, or 2), and35 # decides both whether to generate .pyc files and what level of36 # optimization to use.37 38 user_options = [39 ('install-dir=', 'd', "directory to install to"),40 ('build-dir=', 'b', "build directory (where to install from)"),41 ('force', 'f', "force installation (overwrite existing files)"),42 ('compile', 'c', "compile .py to .pyc [default]"),43 ('no-compile', None, "don't compile .py files"),44 (45 'optimize=',46 'O',47 "also compile with optimization: -O1 for \"python -O\", "48 "-O2 for \"python -OO\", and -O0 to disable [default: -O0]",49 ),50 ('skip-build', None, "skip the build steps"),51 ]52 53 boolean_options: ClassVar[list[str]] = ['force', 'compile', 'skip-build']54 negative_opt: ClassVar[dict[str, str]] = {'no-compile': 'compile'}55 56 def initialize_options(self):57 # let the 'install' command dictate our installation directory58 self.install_dir = None59 self.build_dir = None60 self.force = False61 self.compile = None62 self.optimize = None63 self.skip_build = None64 65 def finalize_options(self) -> None:66 # Get all the information we need to install pure Python modules67 # from the umbrella 'install' command -- build (source) directory,68 # install (target) directory, and whether to compile .py files.69 self.set_undefined_options(70 'install',71 ('build_lib', 'build_dir'),72 ('install_lib', 'install_dir'),73 ('force', 'force'),74 ('compile', 'compile'),75 ('optimize', 'optimize'),76 ('skip_build', 'skip_build'),77 )78 79 if self.compile is None:80 self.compile = True81 if self.optimize is None:82 self.optimize = False83 84 if not isinstance(self.optimize, int):85 try:86 self.optimize = int(self.optimize)87 except ValueError:88 pass89 if self.optimize not in (0, 1, 2):90 raise DistutilsOptionError("optimize must be 0, 1, or 2")91 92 def run(self) -> None:93 # Make sure we have built everything we need first94 self.build()95 96 # Install everything: simply dump the entire contents of the build97 # directory to the installation directory (that's the beauty of98 # having a build directory!)99 outfiles = self.install()100 101 # (Optionally) compile .py to .pyc102 if outfiles is not None and self.distribution.has_pure_modules():103 self.byte_compile(outfiles)104 105 # -- Top-level worker functions ------------------------------------106 # (called from 'run()')107 108 def build(self) -> None:109 if not self.skip_build:110 if self.distribution.has_pure_modules():111 self.run_command('build_py')112 if self.distribution.has_ext_modules():113 self.run_command('build_ext')114 115 # Any: https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#the-any-trick116 def install(self) -> list[str] | Any:117 if os.path.isdir(self.build_dir):118 outfiles = self.copy_tree(self.build_dir, self.install_dir)119 else:120 self.warn(121 f"'{self.build_dir}' does not exist -- no Python modules to install"122 )123 return124 return outfiles125 126 def byte_compile(self, files) -> None:127 if sys.dont_write_bytecode:128 self.warn('byte-compiling is disabled, skipping.')129 return130 131 from ..util import byte_compile132 133 # Get the "--root" directory supplied to the "install" command,134 # and use it as a prefix to strip off the purported filename135 # encoded in bytecode files. This is far from complete, but it136 # should at least generate usable bytecode in RPM distributions.137 install_root = self.get_finalized_command('install').root138 139 if self.compile:140 byte_compile(141 files,142 optimize=0,143 force=self.force,144 prefix=install_root,145 dry_run=self.dry_run,146 )147 if self.optimize > 0:148 byte_compile(149 files,150 optimize=self.optimize,151 force=self.force,152 prefix=install_root,153 verbose=self.verbose,154 dry_run=self.dry_run,155 )156 157 # -- Utility methods -----------------------------------------------158 159 def _mutate_outputs(self, has_any, build_cmd, cmd_option, output_dir):160 if not has_any:161 return []162 163 build_cmd = self.get_finalized_command(build_cmd)164 build_files = build_cmd.get_outputs()165 build_dir = getattr(build_cmd, cmd_option)166 167 prefix_len = len(build_dir) + len(os.sep)168 outputs = [os.path.join(output_dir, file[prefix_len:]) for file in build_files]169 170 return outputs171 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, the176 # 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 continue181 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.optimize189 )190 )191 192 return bytecode_files193 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 command199 were actually run. Not affected by the "dry-run" flag or whether200 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_outputs221 222 def get_inputs(self):223 """Get the list of files that are input to this command, ie. the224 files that get installed as they are named in the build tree.225 The files in this list correspond one-to-one to the output226 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 inputs239 