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