comparison env/lib/python3.7/site-packages/setuptools/build_meta.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
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 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 def _to_str(s):
79 """
80 Convert a filename to a string (on Python 2, explicitly
81 a byte string, not Unicode) as distutils checks for the
82 exact type str.
83 """
84 if sys.version_info[0] == 2 and not isinstance(s, str):
85 # Assume it's Unicode, as that's what the PEP says
86 # should be provided.
87 return s.encode(sys.getfilesystemencoding())
88 return s
89
90
91 def _get_immediate_subdirectories(a_dir):
92 return [name for name in os.listdir(a_dir)
93 if os.path.isdir(os.path.join(a_dir, name))]
94
95
96 def _file_with_extension(directory, extension):
97 matching = (
98 f for f in os.listdir(directory)
99 if f.endswith(extension)
100 )
101 file, = matching
102 return file
103
104
105 def _open_setup_script(setup_script):
106 if not os.path.exists(setup_script):
107 # Supply a default setup.py
108 return io.StringIO(u"from setuptools import setup; setup()")
109
110 return getattr(tokenize, 'open', open)(setup_script)
111
112
113 class _BuildMetaBackend(object):
114
115 def _fix_config(self, config_settings):
116 config_settings = config_settings or {}
117 config_settings.setdefault('--global-option', [])
118 return config_settings
119
120 def _get_build_requires(self, config_settings, requirements):
121 config_settings = self._fix_config(config_settings)
122
123 sys.argv = sys.argv[:1] + ['egg_info'] + \
124 config_settings["--global-option"]
125 try:
126 with Distribution.patch():
127 self.run_setup()
128 except SetupRequirementsError as e:
129 requirements += e.specifiers
130
131 return requirements
132
133 def run_setup(self, setup_script='setup.py'):
134 # Note that we can reuse our build directory between calls
135 # Correctness comes first, then optimization later
136 __file__ = setup_script
137 __name__ = '__main__'
138
139 with _open_setup_script(__file__) as f:
140 code = f.read().replace(r'\r\n', r'\n')
141
142 exec(compile(code, __file__, 'exec'), locals())
143
144 def get_requires_for_build_wheel(self, config_settings=None):
145 config_settings = self._fix_config(config_settings)
146 return self._get_build_requires(config_settings, requirements=['wheel'])
147
148 def get_requires_for_build_sdist(self, config_settings=None):
149 config_settings = self._fix_config(config_settings)
150 return self._get_build_requires(config_settings, requirements=[])
151
152 def prepare_metadata_for_build_wheel(self, metadata_directory,
153 config_settings=None):
154 sys.argv = sys.argv[:1] + ['dist_info', '--egg-base',
155 _to_str(metadata_directory)]
156 self.run_setup()
157
158 dist_info_directory = metadata_directory
159 while True:
160 dist_infos = [f for f in os.listdir(dist_info_directory)
161 if f.endswith('.dist-info')]
162
163 if (len(dist_infos) == 0 and
164 len(_get_immediate_subdirectories(dist_info_directory)) == 1):
165
166 dist_info_directory = os.path.join(
167 dist_info_directory, os.listdir(dist_info_directory)[0])
168 continue
169
170 assert len(dist_infos) == 1
171 break
172
173 # PEP 517 requires that the .dist-info directory be placed in the
174 # metadata_directory. To comply, we MUST copy the directory to the root
175 if dist_info_directory != metadata_directory:
176 shutil.move(
177 os.path.join(dist_info_directory, dist_infos[0]),
178 metadata_directory)
179 shutil.rmtree(dist_info_directory, ignore_errors=True)
180
181 return dist_infos[0]
182
183 def _build_with_temp_dir(self, setup_command, result_extension,
184 result_directory, config_settings):
185 config_settings = self._fix_config(config_settings)
186 result_directory = os.path.abspath(result_directory)
187
188 # Build in a temporary directory, then copy to the target.
189 makedirs(result_directory, exist_ok=True)
190 with TemporaryDirectory(dir=result_directory) as tmp_dist_dir:
191 sys.argv = (sys.argv[:1] + setup_command +
192 ['--dist-dir', tmp_dist_dir] +
193 config_settings["--global-option"])
194 self.run_setup()
195
196 result_basename = _file_with_extension(tmp_dist_dir, result_extension)
197 result_path = os.path.join(result_directory, result_basename)
198 if os.path.exists(result_path):
199 # os.rename will fail overwriting on non-Unix.
200 os.remove(result_path)
201 os.rename(os.path.join(tmp_dist_dir, result_basename), result_path)
202
203 return result_basename
204
205
206 def build_wheel(self, wheel_directory, config_settings=None,
207 metadata_directory=None):
208 return self._build_with_temp_dir(['bdist_wheel'], '.whl',
209 wheel_directory, config_settings)
210
211 def build_sdist(self, sdist_directory, config_settings=None):
212 return self._build_with_temp_dir(['sdist', '--formats', 'gztar'],
213 '.tar.gz', sdist_directory,
214 config_settings)
215
216
217 class _BuildMetaLegacyBackend(_BuildMetaBackend):
218 """Compatibility backend for setuptools
219
220 This is a version of setuptools.build_meta that endeavors to maintain backwards
221 compatibility with pre-PEP 517 modes of invocation. It exists as a temporary
222 bridge between the old packaging mechanism and the new packaging mechanism,
223 and will eventually be removed.
224 """
225 def run_setup(self, setup_script='setup.py'):
226 # In order to maintain compatibility with scripts assuming that
227 # the setup.py script is in a directory on the PYTHONPATH, inject
228 # '' into sys.path. (pypa/setuptools#1642)
229 sys_path = list(sys.path) # Save the original path
230
231 script_dir = os.path.dirname(os.path.abspath(setup_script))
232 if script_dir not in sys.path:
233 sys.path.insert(0, script_dir)
234
235 try:
236 super(_BuildMetaLegacyBackend,
237 self).run_setup(setup_script=setup_script)
238 finally:
239 # While PEP 517 frontends should be calling each hook in a fresh
240 # subprocess according to the standard (and thus it should not be
241 # strictly necessary to restore the old sys.path), we'll restore
242 # the original path so that the path manipulation does not persist
243 # within the hook after run_setup is called.
244 sys.path[:] = sys_path
245
246 # The primary backend
247 _BACKEND = _BuildMetaBackend()
248
249 get_requires_for_build_wheel = _BACKEND.get_requires_for_build_wheel
250 get_requires_for_build_sdist = _BACKEND.get_requires_for_build_sdist
251 prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel
252 build_wheel = _BACKEND.build_wheel
253 build_sdist = _BACKEND.build_sdist
254
255
256 # The legacy backend
257 __legacy__ = _BuildMetaLegacyBackend()