comparison env/lib/python3.9/site-packages/setuptools/build_meta.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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 import tempfile
36
37 import setuptools
38 import distutils
39
40 from pkg_resources import parse_requirements
41
42 __all__ = ['get_requires_for_build_sdist',
43 'get_requires_for_build_wheel',
44 'prepare_metadata_for_build_wheel',
45 'build_wheel',
46 'build_sdist',
47 '__legacy__',
48 'SetupRequirementsError']
49
50
51 class SetupRequirementsError(BaseException):
52 def __init__(self, specifiers):
53 self.specifiers = specifiers
54
55
56 class Distribution(setuptools.dist.Distribution):
57 def fetch_build_eggs(self, specifiers):
58 specifier_list = list(map(str, parse_requirements(specifiers)))
59
60 raise SetupRequirementsError(specifier_list)
61
62 @classmethod
63 @contextlib.contextmanager
64 def patch(cls):
65 """
66 Replace
67 distutils.dist.Distribution with this class
68 for the duration of this context.
69 """
70 orig = distutils.core.Distribution
71 distutils.core.Distribution = cls
72 try:
73 yield
74 finally:
75 distutils.core.Distribution = orig
76
77
78 @contextlib.contextmanager
79 def no_install_setup_requires():
80 """Temporarily disable installing setup_requires
81
82 Under PEP 517, the backend reports build dependencies to the frontend,
83 and the frontend is responsible for ensuring they're installed.
84 So setuptools (acting as a backend) should not try to install them.
85 """
86 orig = setuptools._install_setup_requires
87 setuptools._install_setup_requires = lambda attrs: None
88 try:
89 yield
90 finally:
91 setuptools._install_setup_requires = orig
92
93
94 def _get_immediate_subdirectories(a_dir):
95 return [name for name in os.listdir(a_dir)
96 if os.path.isdir(os.path.join(a_dir, name))]
97
98
99 def _file_with_extension(directory, extension):
100 matching = (
101 f for f in os.listdir(directory)
102 if f.endswith(extension)
103 )
104 file, = matching
105 return file
106
107
108 def _open_setup_script(setup_script):
109 if not os.path.exists(setup_script):
110 # Supply a default setup.py
111 return io.StringIO(u"from setuptools import setup; setup()")
112
113 return getattr(tokenize, 'open', open)(setup_script)
114
115
116 class _BuildMetaBackend(object):
117
118 def _fix_config(self, config_settings):
119 config_settings = config_settings or {}
120 config_settings.setdefault('--global-option', [])
121 return config_settings
122
123 def _get_build_requires(self, config_settings, requirements):
124 config_settings = self._fix_config(config_settings)
125
126 sys.argv = sys.argv[:1] + ['egg_info'] + \
127 config_settings["--global-option"]
128 try:
129 with Distribution.patch():
130 self.run_setup()
131 except SetupRequirementsError as e:
132 requirements += e.specifiers
133
134 return requirements
135
136 def run_setup(self, setup_script='setup.py'):
137 # Note that we can reuse our build directory between calls
138 # Correctness comes first, then optimization later
139 __file__ = setup_script
140 __name__ = '__main__'
141
142 with _open_setup_script(__file__) as f:
143 code = f.read().replace(r'\r\n', r'\n')
144
145 exec(compile(code, __file__, 'exec'), locals())
146
147 def get_requires_for_build_wheel(self, config_settings=None):
148 config_settings = self._fix_config(config_settings)
149 return self._get_build_requires(
150 config_settings, requirements=['wheel'])
151
152 def get_requires_for_build_sdist(self, config_settings=None):
153 config_settings = self._fix_config(config_settings)
154 return self._get_build_requires(config_settings, requirements=[])
155
156 def prepare_metadata_for_build_wheel(self, metadata_directory,
157 config_settings=None):
158 sys.argv = sys.argv[:1] + [
159 'dist_info', '--egg-base', metadata_directory]
160 with no_install_setup_requires():
161 self.run_setup()
162
163 dist_info_directory = metadata_directory
164 while True:
165 dist_infos = [f for f in os.listdir(dist_info_directory)
166 if f.endswith('.dist-info')]
167
168 if (
169 len(dist_infos) == 0 and
170 len(_get_immediate_subdirectories(dist_info_directory)) == 1
171 ):
172
173 dist_info_directory = os.path.join(
174 dist_info_directory, os.listdir(dist_info_directory)[0])
175 continue
176
177 assert len(dist_infos) == 1
178 break
179
180 # PEP 517 requires that the .dist-info directory be placed in the
181 # metadata_directory. To comply, we MUST copy the directory to the root
182 if dist_info_directory != metadata_directory:
183 shutil.move(
184 os.path.join(dist_info_directory, dist_infos[0]),
185 metadata_directory)
186 shutil.rmtree(dist_info_directory, ignore_errors=True)
187
188 return dist_infos[0]
189
190 def _build_with_temp_dir(self, setup_command, result_extension,
191 result_directory, config_settings):
192 config_settings = self._fix_config(config_settings)
193 result_directory = os.path.abspath(result_directory)
194
195 # Build in a temporary directory, then copy to the target.
196 os.makedirs(result_directory, exist_ok=True)
197 with tempfile.TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
198 sys.argv = (sys.argv[:1] + setup_command +
199 ['--dist-dir', tmp_dist_dir] +
200 config_settings["--global-option"])
201 with no_install_setup_requires():
202 self.run_setup()
203
204 result_basename = _file_with_extension(
205 tmp_dist_dir, result_extension)
206 result_path = os.path.join(result_directory, result_basename)
207 if os.path.exists(result_path):
208 # os.rename will fail overwriting on non-Unix.
209 os.remove(result_path)
210 os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)
211
212 return result_basename
213
214 def build_wheel(self, wheel_directory, config_settings=None,
215 metadata_directory=None):
216 return self._build_with_temp_dir(['bdist_wheel'], '.whl',
217 wheel_directory, config_settings)
218
219 def build_sdist(self, sdist_directory, config_settings=None):
220 return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],
221 '.tar.gz', sdist_directory,
222 config_settings)
223
224
225 class _BuildMetaLegacyBackend(_BuildMetaBackend):
226 """Compatibility backend for setuptools
227
228 This is a version of setuptools.build_meta that endeavors
229 to maintain backwards
230 compatibility with pre-PEP 517 modes of invocation. It
231 exists as a temporary
232 bridge between the old packaging mechanism and the new
233 packaging mechanism,
234 and will eventually be removed.
235 """
236 def run_setup(self, setup_script='setup.py'):
237 # In order to maintain compatibility with scripts assuming that
238 # the setup.py script is in a directory on the PYTHONPATH, inject
239 # '' into sys.path. (pypa/setuptools#1642)
240 sys_path = list(sys.path) # Save the original path
241
242 script_dir = os.path.dirname(os.path.abspath(setup_script))
243 if script_dir not in sys.path:
244 sys.path.insert(0, script_dir)
245
246 # Some setup.py scripts (e.g. in pygame and numpy) use sys.argv[0] to
247 # get the directory of the source code. They expect it to refer to the
248 # setup.py script.
249 sys_argv_0 = sys.argv[0]
250 sys.argv[0] = setup_script
251
252 try:
253 super(_BuildMetaLegacyBackend,
254 self).run_setup(setup_script=setup_script)
255 finally:
256 # While PEP 517 frontends should be calling each hook in a fresh
257 # subprocess according to the standard (and thus it should not be
258 # strictly necessary to restore the old sys.path), we'll restore
259 # the original path so that the path manipulation does not persist
260 # within the hook after run_setup is called.
261 sys.path[:] = sys_path
262 sys.argv[0] = sys_argv_0
263
264
265 # The primary backend
266 _BACKEND = _BuildMetaBackend()
267
268 get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
269 get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
270 prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
271 build_wheel = _BACKEND.build_wheel
272 build_sdist = _BACKEND.build_sdist
273
274
275 # The legacy backend
276 __legacy__ = _BuildMetaLegacyBackend()