comparison lib/python3.8/site-packages/setuptools/build_meta.py @ 1:64071f2a4cf0 draft default tip

Deleted selected files
author guerler
date Mon, 27 Jul 2020 03:55:49 -0400
parents 9e54283cc701
children
comparison
equal deleted inserted replaced
0:9e54283cc701 1:64071f2a4cf0
1 """A PEP 517 interface to setuptools
2
3 Previously, when a user or a command line tool (let's call it a "frontend")
4 needed to make a request of setuptools to take a certain action, for
5 example, generating a list of installation requirements, the frontend would
6 would call "setup.py egg_info" or "setup.py bdist_wheel" on the command line.
7
8 PEP 517 defines a different method of interfacing with setuptools. Rather
9 than calling "setup.py" directly, the frontend should:
10
11 1. Set the current directory to the directory with a setup.py file
12 2. Import this module into a safe python interpreter (one in which
13 setuptools can potentially set global variables or crash hard).
14 3. Call one of the functions defined in PEP 517.
15
16 What each function does is defined in PEP 517. However, here is a "casual"
17 definition of the functions (this definition should not be relied on for
18 bug reports or API stability):
19
20 - `build_wheel`: build a wheel in the folder and return the basename
21 - `get_requires_for_build_wheel`: get the `setup_requires` to build
22 - `prepare_metadata_for_build_wheel`: get the `install_requires`
23 - `build_sdist`: build an sdist in the folder and return the basename
24 - `get_requires_for_build_sdist`: get the `setup_requires` to build
25
26 Again, this is not a formal definition! Just a "taste" of the module.
27 """
28
29 import io
30 import os
31 import sys
32 import tokenize
33 import shutil
34 import contextlib
35
36 import setuptools
37 import distutils
38 from setuptools.py31compat import TemporaryDirectory
39
40 from pkg_resources import parse_requirements
41 from pkg_resources.py31compat import makedirs
42
43 __all__ = ['get_requires_for_build_sdist',
44 'get_requires_for_build_wheel',
45 'prepare_metadata_for_build_wheel',
46 'build_wheel',
47 'build_sdist',
48 '__legacy__',
49 'SetupRequirementsError']
50
51
52 class SetupRequirementsError(BaseException):
53 def __init__(self, specifiers):
54 self.specifiers = specifiers
55
56
57 class Distribution(setuptools.dist.Distribution):
58 def fetch_build_eggs(self, specifiers):
59 specifier_list = list(map(str, parse_requirements(specifiers)))
60
61 raise SetupRequirementsError(specifier_list)
62
63 @classmethod
64 @contextlib.contextmanager
65 def patch(cls):
66 """
67 Replace
68 distutils.dist.Distribution with this class
69 for the duration of this context.
70 """
71 orig = distutils.core.Distribution
72 distutils.core.Distribution = cls
73 try:
74 yield
75 finally:
76 distutils.core.Distribution = orig
77
78
79 def _to_str(s):
80 """
81 Convert a filename to a string (on Python 2, explicitly
82 a byte string, not Unicode) as distutils checks for the
83 exact type str.
84 """
85 if sys.version_info[0] == 2 and not isinstance(s, str):
86 # Assume it's Unicode, as that's what the PEP says
87 # should be provided.
88 return s.encode(sys.getfilesystemencoding())
89 return s
90
91
92 def _get_immediate_subdirectories(a_dir):
93 return [name for name in os.listdir(a_dir)
94 if os.path.isdir(os.path.join(a_dir, name))]
95
96
97 def _file_with_extension(directory, extension):
98 matching = (
99 f for f in os.listdir(directory)
100 if f.endswith(extension)
101 )
102 file, = matching
103 return file
104
105
106 def _open_setup_script(setup_script):
107 if not os.path.exists(setup_script):
108 # Supply a default setup.py
109 return io.StringIO(u"from setuptools import setup; setup()")
110
111 return getattr(tokenize, 'open', open)(setup_script)
112
113
114 class _BuildMetaBackend(object):
115
116 def _fix_config(self, config_settings):
117 config_settings = config_settings or {}
118 config_settings.setdefault('--global-option', [])
119 return config_settings
120
121 def _get_build_requires(self, config_settings, requirements):
122 config_settings = self._fix_config(config_settings)
123
124 sys.argv = sys.argv[:1] + ['egg_info'] + \
125 config_settings["--global-option"]
126 try:
127 with Distribution.patch():
128 self.run_setup()
129 except SetupRequirementsError as e:
130 requirements += e.specifiers
131
132 return requirements
133
134 def run_setup(self, setup_script='setup.py'):
135 # Note that we can reuse our build directory between calls
136 # Correctness comes first, then optimization later
137 __file__ = setup_script
138 __name__ = '__main__'
139
140 with _open_setup_script(__file__) as f:
141 code = f.read().replace(r'\r\n', r'\n')
142
143 exec(compile(code, __file__, 'exec'), locals())
144
145 def get_requires_for_build_wheel(self, config_settings=None):
146 config_settings = self._fix_config(config_settings)
147 return self._get_build_requires(
148 config_settings, requirements=['wheel'])
149
150 def get_requires_for_build_sdist(self, config_settings=None):
151 config_settings = self._fix_config(config_settings)
152 return self._get_build_requires(config_settings, requirements=[])
153
154 def prepare_metadata_for_build_wheel(self, metadata_directory,
155 config_settings=None):
156 sys.argv = sys.argv[:1] + ['dist_info', '--egg-base',
157 _to_str(metadata_directory)]
158 self.run_setup()
159
160 dist_info_directory = metadata_directory
161 while True:
162 dist_infos = [f for f in os.listdir(dist_info_directory)
163 if f.endswith('.dist-info')]
164
165 if (
166 len(dist_infos) == 0 and
167 len(_get_immediate_subdirectories(dist_info_directory)) == 1
168 ):
169
170 dist_info_directory = os.path.join(
171 dist_info_directory, os.listdir(dist_info_directory)[0])
172 continue
173
174 assert len(dist_infos) == 1
175 break
176
177 # PEP 517 requires that the .dist-info directory be placed in the
178 # metadata_directory. To comply, we MUST copy the directory to the root
179 if dist_info_directory != metadata_directory:
180 shutil.move(
181 os.path.join(dist_info_directory, dist_infos[0]),
182 metadata_directory)
183 shutil.rmtree(dist_info_directory, ignore_errors=True)
184
185 return dist_infos[0]
186
187 def _build_with_temp_dir(self, setup_command, result_extension,
188 result_directory, config_settings):
189 config_settings = self._fix_config(config_settings)
190 result_directory = os.path.abspath(result_directory)
191
192 # Build in a temporary directory, then copy to the target.
193 makedirs(result_directory, exist_ok=True)
194 with TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
195 sys.argv = (sys.argv[:1] + setup_command +
196 ['--dist-dir', tmp_dist_dir] +
197 config_settings["--global-option"])
198 self.run_setup()
199
200 result_basename = _file_with_extension(
201 tmp_dist_dir, result_extension)
202 result_path = os.path.join(result_directory, result_basename)
203 if os.path.exists(result_path):
204 # os.rename will fail overwriting on non-Unix.
205 os.remove(result_path)
206 os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)
207
208 return result_basename
209
210 def build_wheel(self, wheel_directory, config_settings=None,
211 metadata_directory=None):
212 return self._build_with_temp_dir(['bdist_wheel'], '.whl',
213 wheel_directory, config_settings)
214
215 def build_sdist(self, sdist_directory, config_settings=None):
216 return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],
217 '.tar.gz', sdist_directory,
218 config_settings)
219
220
221 class _BuildMetaLegacyBackend(_BuildMetaBackend):
222 """Compatibility backend for setuptools
223
224 This is a version of setuptools.build_meta that endeavors
225 to maintain backwards
226 compatibility with pre-PEP 517 modes of invocation. It
227 exists as a temporary
228 bridge between the old packaging mechanism and the new
229 packaging mechanism,
230 and will eventually be removed.
231 """
232 def run_setup(self, setup_script='setup.py'):
233 # In order to maintain compatibility with scripts assuming that
234 # the setup.py script is in a directory on the PYTHONPATH, inject
235 # '' into sys.path. (pypa/setuptools#1642)
236 sys_path = list(sys.path) # Save the original path
237
238 script_dir = os.path.dirname(os.path.abspath(setup_script))
239 if script_dir not in sys.path:
240 sys.path.insert(0, script_dir)
241
242 # Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to
243 # get the directory of the source code. They expect it to refer to the
244 # setup.py script.
245 sys_argv_0 = sys.argv[0]
246 sys.argv[0] = setup_script
247
248 try:
249 super(_BuildMetaLegacyBackend,
250 self).run_setup(setup_script=setup_script)
251 finally:
252 # While PEP 517 frontends should be calling each hook in a fresh
253 # subprocess according to the standard (and thus it should not be
254 # strictly necessary to restore the old sys.path), we'll restore
255 # the original path so that the path manipulation does not persist
256 # within the hook after run_setup is called.
257 sys.path[:] = sys_path
258 sys.argv[0] = sys_argv_0
259
260
261 # The primary backend
262 _BACKEND = _BuildMetaBackend()
263
264 get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
265 get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
266 prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
267 build_wheel = _BACKEND.build_wheel
268 build_sdist = _BACKEND.build_sdist
269
270
271 # The legacy backend
272 __legacy__ = _BuildMetaLegacyBackend()