840d3403c4158f9c7298ca31d298f0322ec0f73e
[SubU] /
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 ..core import Command
11 from ..errors import DistutilsOptionError
12
13
14 # Extension for Python source files.
15 PYTHON_SOURCE_EXTENSION = ".py"
16
17
18 class install_lib(Command):
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 ..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