Mercurial > repos > guerler > hhblits
comparison lib/python3.8/site-packages/pip/_vendor/pep517/wrappers.py @ 0:9e54283cc701 draft
"planemo upload commit d12c32a45bcd441307e632fca6d9af7d60289d44"
author | guerler |
---|---|
date | Mon, 27 Jul 2020 03:47:31 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:9e54283cc701 |
---|---|
1 import threading | |
2 from contextlib import contextmanager | |
3 import os | |
4 from os.path import dirname, abspath, join as pjoin | |
5 import shutil | |
6 from subprocess import check_call, check_output, STDOUT | |
7 import sys | |
8 from tempfile import mkdtemp | |
9 | |
10 from . import compat | |
11 | |
12 _in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py') | |
13 | |
14 | |
15 @contextmanager | |
16 def tempdir(): | |
17 td = mkdtemp() | |
18 try: | |
19 yield td | |
20 finally: | |
21 shutil.rmtree(td) | |
22 | |
23 | |
24 class BackendUnavailable(Exception): | |
25 """Will be raised if the backend cannot be imported in the hook process.""" | |
26 def __init__(self, traceback): | |
27 self.traceback = traceback | |
28 | |
29 | |
30 class BackendInvalid(Exception): | |
31 """Will be raised if the backend is invalid.""" | |
32 def __init__(self, backend_name, backend_path, message): | |
33 self.backend_name = backend_name | |
34 self.backend_path = backend_path | |
35 self.message = message | |
36 | |
37 | |
38 class HookMissing(Exception): | |
39 """Will be raised on missing hooks.""" | |
40 def __init__(self, hook_name): | |
41 super(HookMissing, self).__init__(hook_name) | |
42 self.hook_name = hook_name | |
43 | |
44 | |
45 class UnsupportedOperation(Exception): | |
46 """May be raised by build_sdist if the backend indicates that it can't.""" | |
47 def __init__(self, traceback): | |
48 self.traceback = traceback | |
49 | |
50 | |
51 def default_subprocess_runner(cmd, cwd=None, extra_environ=None): | |
52 """The default method of calling the wrapper subprocess.""" | |
53 env = os.environ.copy() | |
54 if extra_environ: | |
55 env.update(extra_environ) | |
56 | |
57 check_call(cmd, cwd=cwd, env=env) | |
58 | |
59 | |
60 def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None): | |
61 """A method of calling the wrapper subprocess while suppressing output.""" | |
62 env = os.environ.copy() | |
63 if extra_environ: | |
64 env.update(extra_environ) | |
65 | |
66 check_output(cmd, cwd=cwd, env=env, stderr=STDOUT) | |
67 | |
68 | |
69 def norm_and_check(source_tree, requested): | |
70 """Normalise and check a backend path. | |
71 | |
72 Ensure that the requested backend path is specified as a relative path, | |
73 and resolves to a location under the given source tree. | |
74 | |
75 Return an absolute version of the requested path. | |
76 """ | |
77 if os.path.isabs(requested): | |
78 raise ValueError("paths must be relative") | |
79 | |
80 abs_source = os.path.abspath(source_tree) | |
81 abs_requested = os.path.normpath(os.path.join(abs_source, requested)) | |
82 # We have to use commonprefix for Python 2.7 compatibility. So we | |
83 # normalise case to avoid problems because commonprefix is a character | |
84 # based comparison :-( | |
85 norm_source = os.path.normcase(abs_source) | |
86 norm_requested = os.path.normcase(abs_requested) | |
87 if os.path.commonprefix([norm_source, norm_requested]) != norm_source: | |
88 raise ValueError("paths must be inside source tree") | |
89 | |
90 return abs_requested | |
91 | |
92 | |
93 class Pep517HookCaller(object): | |
94 """A wrapper around a source directory to be built with a PEP 517 backend. | |
95 | |
96 source_dir : The path to the source directory, containing pyproject.toml. | |
97 build_backend : The build backend spec, as per PEP 517, from | |
98 pyproject.toml. | |
99 backend_path : The backend path, as per PEP 517, from pyproject.toml. | |
100 runner : A callable that invokes the wrapper subprocess. | |
101 | |
102 The 'runner', if provided, must expect the following: | |
103 cmd : a list of strings representing the command and arguments to | |
104 execute, as would be passed to e.g. 'subprocess.check_call'. | |
105 cwd : a string representing the working directory that must be | |
106 used for the subprocess. Corresponds to the provided source_dir. | |
107 extra_environ : a dict mapping environment variable names to values | |
108 which must be set for the subprocess execution. | |
109 """ | |
110 def __init__( | |
111 self, | |
112 source_dir, | |
113 build_backend, | |
114 backend_path=None, | |
115 runner=None, | |
116 ): | |
117 if runner is None: | |
118 runner = default_subprocess_runner | |
119 | |
120 self.source_dir = abspath(source_dir) | |
121 self.build_backend = build_backend | |
122 if backend_path: | |
123 backend_path = [ | |
124 norm_and_check(self.source_dir, p) for p in backend_path | |
125 ] | |
126 self.backend_path = backend_path | |
127 self._subprocess_runner = runner | |
128 | |
129 # TODO: Is this over-engineered? Maybe frontends only need to | |
130 # set this when creating the wrapper, not on every call. | |
131 @contextmanager | |
132 def subprocess_runner(self, runner): | |
133 """A context manager for temporarily overriding the default subprocess | |
134 runner. | |
135 """ | |
136 prev = self._subprocess_runner | |
137 self._subprocess_runner = runner | |
138 yield | |
139 self._subprocess_runner = prev | |
140 | |
141 def get_requires_for_build_wheel(self, config_settings=None): | |
142 """Identify packages required for building a wheel | |
143 | |
144 Returns a list of dependency specifications, e.g.: | |
145 ["wheel >= 0.25", "setuptools"] | |
146 | |
147 This does not include requirements specified in pyproject.toml. | |
148 It returns the result of calling the equivalently named hook in a | |
149 subprocess. | |
150 """ | |
151 return self._call_hook('get_requires_for_build_wheel', { | |
152 'config_settings': config_settings | |
153 }) | |
154 | |
155 def prepare_metadata_for_build_wheel( | |
156 self, metadata_directory, config_settings=None, | |
157 _allow_fallback=True): | |
158 """Prepare a *.dist-info folder with metadata for this project. | |
159 | |
160 Returns the name of the newly created folder. | |
161 | |
162 If the build backend defines a hook with this name, it will be called | |
163 in a subprocess. If not, the backend will be asked to build a wheel, | |
164 and the dist-info extracted from that (unless _allow_fallback is | |
165 False). | |
166 """ | |
167 return self._call_hook('prepare_metadata_for_build_wheel', { | |
168 'metadata_directory': abspath(metadata_directory), | |
169 'config_settings': config_settings, | |
170 '_allow_fallback': _allow_fallback, | |
171 }) | |
172 | |
173 def build_wheel( | |
174 self, wheel_directory, config_settings=None, | |
175 metadata_directory=None): | |
176 """Build a wheel from this project. | |
177 | |
178 Returns the name of the newly created file. | |
179 | |
180 In general, this will call the 'build_wheel' hook in the backend. | |
181 However, if that was previously called by | |
182 'prepare_metadata_for_build_wheel', and the same metadata_directory is | |
183 used, the previously built wheel will be copied to wheel_directory. | |
184 """ | |
185 if metadata_directory is not None: | |
186 metadata_directory = abspath(metadata_directory) | |
187 return self._call_hook('build_wheel', { | |
188 'wheel_directory': abspath(wheel_directory), | |
189 'config_settings': config_settings, | |
190 'metadata_directory': metadata_directory, | |
191 }) | |
192 | |
193 def get_requires_for_build_sdist(self, config_settings=None): | |
194 """Identify packages required for building a wheel | |
195 | |
196 Returns a list of dependency specifications, e.g.: | |
197 ["setuptools >= 26"] | |
198 | |
199 This does not include requirements specified in pyproject.toml. | |
200 It returns the result of calling the equivalently named hook in a | |
201 subprocess. | |
202 """ | |
203 return self._call_hook('get_requires_for_build_sdist', { | |
204 'config_settings': config_settings | |
205 }) | |
206 | |
207 def build_sdist(self, sdist_directory, config_settings=None): | |
208 """Build an sdist from this project. | |
209 | |
210 Returns the name of the newly created file. | |
211 | |
212 This calls the 'build_sdist' backend hook in a subprocess. | |
213 """ | |
214 return self._call_hook('build_sdist', { | |
215 'sdist_directory': abspath(sdist_directory), | |
216 'config_settings': config_settings, | |
217 }) | |
218 | |
219 def _call_hook(self, hook_name, kwargs): | |
220 # On Python 2, pytoml returns Unicode values (which is correct) but the | |
221 # environment passed to check_call needs to contain string values. We | |
222 # convert here by encoding using ASCII (the backend can only contain | |
223 # letters, digits and _, . and : characters, and will be used as a | |
224 # Python identifier, so non-ASCII content is wrong on Python 2 in | |
225 # any case). | |
226 # For backend_path, we use sys.getfilesystemencoding. | |
227 if sys.version_info[0] == 2: | |
228 build_backend = self.build_backend.encode('ASCII') | |
229 else: | |
230 build_backend = self.build_backend | |
231 extra_environ = {'PEP517_BUILD_BACKEND': build_backend} | |
232 | |
233 if self.backend_path: | |
234 backend_path = os.pathsep.join(self.backend_path) | |
235 if sys.version_info[0] == 2: | |
236 backend_path = backend_path.encode(sys.getfilesystemencoding()) | |
237 extra_environ['PEP517_BACKEND_PATH'] = backend_path | |
238 | |
239 with tempdir() as td: | |
240 hook_input = {'kwargs': kwargs} | |
241 compat.write_json(hook_input, pjoin(td, 'input.json'), | |
242 indent=2) | |
243 | |
244 # Run the hook in a subprocess | |
245 self._subprocess_runner( | |
246 [sys.executable, _in_proc_script, hook_name, td], | |
247 cwd=self.source_dir, | |
248 extra_environ=extra_environ | |
249 ) | |
250 | |
251 data = compat.read_json(pjoin(td, 'output.json')) | |
252 if data.get('unsupported'): | |
253 raise UnsupportedOperation(data.get('traceback', '')) | |
254 if data.get('no_backend'): | |
255 raise BackendUnavailable(data.get('traceback', '')) | |
256 if data.get('backend_invalid'): | |
257 raise BackendInvalid( | |
258 backend_name=self.build_backend, | |
259 backend_path=self.backend_path, | |
260 message=data.get('backend_error', '') | |
261 ) | |
262 if data.get('hook_missing'): | |
263 raise HookMissing(hook_name) | |
264 return data['return_val'] | |
265 | |
266 | |
267 class LoggerWrapper(threading.Thread): | |
268 """ | |
269 Read messages from a pipe and redirect them | |
270 to a logger (see python's logging module). | |
271 """ | |
272 | |
273 def __init__(self, logger, level): | |
274 threading.Thread.__init__(self) | |
275 self.daemon = True | |
276 | |
277 self.logger = logger | |
278 self.level = level | |
279 | |
280 # create the pipe and reader | |
281 self.fd_read, self.fd_write = os.pipe() | |
282 self.reader = os.fdopen(self.fd_read) | |
283 | |
284 self.start() | |
285 | |
286 def fileno(self): | |
287 return self.fd_write | |
288 | |
289 @staticmethod | |
290 def remove_newline(msg): | |
291 return msg[:-1] if msg.endswith(os.linesep) else msg | |
292 | |
293 def run(self): | |
294 for line in self.reader: | |
295 self._write(self.remove_newline(line)) | |
296 | |
297 def _write(self, message): | |
298 self.logger.log(self.level, message) |