Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/setuptools/sandbox.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author | bcclaywell |
---|---|
date | Mon, 12 Oct 2015 17:43:33 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:d67268158946 |
---|---|
1 import os | |
2 import sys | |
3 import tempfile | |
4 import operator | |
5 import functools | |
6 import itertools | |
7 import re | |
8 import contextlib | |
9 import pickle | |
10 | |
11 import pkg_resources | |
12 | |
13 if os.name == "java": | |
14 import org.python.modules.posix.PosixModule as _os | |
15 else: | |
16 _os = sys.modules[os.name] | |
17 try: | |
18 _file = file | |
19 except NameError: | |
20 _file = None | |
21 _open = open | |
22 from distutils.errors import DistutilsError | |
23 from pkg_resources import working_set | |
24 | |
25 from setuptools import compat | |
26 from setuptools.compat import builtins | |
27 | |
28 __all__ = [ | |
29 "AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup", | |
30 ] | |
31 | |
32 def _execfile(filename, globals, locals=None): | |
33 """ | |
34 Python 3 implementation of execfile. | |
35 """ | |
36 mode = 'rb' | |
37 # Python 2.6 compile requires LF for newlines, so use deprecated | |
38 # Universal newlines support. | |
39 if sys.version_info < (2, 7): | |
40 mode += 'U' | |
41 with open(filename, mode) as stream: | |
42 script = stream.read() | |
43 if locals is None: | |
44 locals = globals | |
45 code = compile(script, filename, 'exec') | |
46 exec(code, globals, locals) | |
47 | |
48 | |
49 @contextlib.contextmanager | |
50 def save_argv(repl=None): | |
51 saved = sys.argv[:] | |
52 if repl is not None: | |
53 sys.argv[:] = repl | |
54 try: | |
55 yield saved | |
56 finally: | |
57 sys.argv[:] = saved | |
58 | |
59 | |
60 @contextlib.contextmanager | |
61 def save_path(): | |
62 saved = sys.path[:] | |
63 try: | |
64 yield saved | |
65 finally: | |
66 sys.path[:] = saved | |
67 | |
68 | |
69 @contextlib.contextmanager | |
70 def override_temp(replacement): | |
71 """ | |
72 Monkey-patch tempfile.tempdir with replacement, ensuring it exists | |
73 """ | |
74 if not os.path.isdir(replacement): | |
75 os.makedirs(replacement) | |
76 | |
77 saved = tempfile.tempdir | |
78 | |
79 tempfile.tempdir = replacement | |
80 | |
81 try: | |
82 yield | |
83 finally: | |
84 tempfile.tempdir = saved | |
85 | |
86 | |
87 @contextlib.contextmanager | |
88 def pushd(target): | |
89 saved = os.getcwd() | |
90 os.chdir(target) | |
91 try: | |
92 yield saved | |
93 finally: | |
94 os.chdir(saved) | |
95 | |
96 | |
97 class UnpickleableException(Exception): | |
98 """ | |
99 An exception representing another Exception that could not be pickled. | |
100 """ | |
101 @classmethod | |
102 def dump(cls, type, exc): | |
103 """ | |
104 Always return a dumped (pickled) type and exc. If exc can't be pickled, | |
105 wrap it in UnpickleableException first. | |
106 """ | |
107 try: | |
108 return pickle.dumps(type), pickle.dumps(exc) | |
109 except Exception: | |
110 return cls.dump(cls, cls(repr(exc))) | |
111 | |
112 | |
113 class ExceptionSaver: | |
114 """ | |
115 A Context Manager that will save an exception, serialized, and restore it | |
116 later. | |
117 """ | |
118 def __enter__(self): | |
119 return self | |
120 | |
121 def __exit__(self, type, exc, tb): | |
122 if not exc: | |
123 return | |
124 | |
125 # dump the exception | |
126 self._saved = UnpickleableException.dump(type, exc) | |
127 self._tb = tb | |
128 | |
129 # suppress the exception | |
130 return True | |
131 | |
132 def resume(self): | |
133 "restore and re-raise any exception" | |
134 | |
135 if '_saved' not in vars(self): | |
136 return | |
137 | |
138 type, exc = map(pickle.loads, self._saved) | |
139 compat.reraise(type, exc, self._tb) | |
140 | |
141 | |
142 @contextlib.contextmanager | |
143 def save_modules(): | |
144 """ | |
145 Context in which imported modules are saved. | |
146 | |
147 Translates exceptions internal to the context into the equivalent exception | |
148 outside the context. | |
149 """ | |
150 saved = sys.modules.copy() | |
151 with ExceptionSaver() as saved_exc: | |
152 yield saved | |
153 | |
154 sys.modules.update(saved) | |
155 # remove any modules imported since | |
156 del_modules = ( | |
157 mod_name for mod_name in sys.modules | |
158 if mod_name not in saved | |
159 # exclude any encodings modules. See #285 | |
160 and not mod_name.startswith('encodings.') | |
161 ) | |
162 _clear_modules(del_modules) | |
163 | |
164 saved_exc.resume() | |
165 | |
166 | |
167 def _clear_modules(module_names): | |
168 for mod_name in list(module_names): | |
169 del sys.modules[mod_name] | |
170 | |
171 | |
172 @contextlib.contextmanager | |
173 def save_pkg_resources_state(): | |
174 saved = pkg_resources.__getstate__() | |
175 try: | |
176 yield saved | |
177 finally: | |
178 pkg_resources.__setstate__(saved) | |
179 | |
180 | |
181 @contextlib.contextmanager | |
182 def setup_context(setup_dir): | |
183 temp_dir = os.path.join(setup_dir, 'temp') | |
184 with save_pkg_resources_state(): | |
185 with save_modules(): | |
186 hide_setuptools() | |
187 with save_path(): | |
188 with save_argv(): | |
189 with override_temp(temp_dir): | |
190 with pushd(setup_dir): | |
191 # ensure setuptools commands are available | |
192 __import__('setuptools') | |
193 yield | |
194 | |
195 | |
196 def _needs_hiding(mod_name): | |
197 """ | |
198 >>> _needs_hiding('setuptools') | |
199 True | |
200 >>> _needs_hiding('pkg_resources') | |
201 True | |
202 >>> _needs_hiding('setuptools_plugin') | |
203 False | |
204 >>> _needs_hiding('setuptools.__init__') | |
205 True | |
206 >>> _needs_hiding('distutils') | |
207 True | |
208 """ | |
209 pattern = re.compile('(setuptools|pkg_resources|distutils)(\.|$)') | |
210 return bool(pattern.match(mod_name)) | |
211 | |
212 | |
213 def hide_setuptools(): | |
214 """ | |
215 Remove references to setuptools' modules from sys.modules to allow the | |
216 invocation to import the most appropriate setuptools. This technique is | |
217 necessary to avoid issues such as #315 where setuptools upgrading itself | |
218 would fail to find a function declared in the metadata. | |
219 """ | |
220 modules = filter(_needs_hiding, sys.modules) | |
221 _clear_modules(modules) | |
222 | |
223 | |
224 def run_setup(setup_script, args): | |
225 """Run a distutils setup script, sandboxed in its directory""" | |
226 setup_dir = os.path.abspath(os.path.dirname(setup_script)) | |
227 with setup_context(setup_dir): | |
228 try: | |
229 sys.argv[:] = [setup_script]+list(args) | |
230 sys.path.insert(0, setup_dir) | |
231 # reset to include setup dir, w/clean callback list | |
232 working_set.__init__() | |
233 working_set.callbacks.append(lambda dist:dist.activate()) | |
234 def runner(): | |
235 ns = dict(__file__=setup_script, __name__='__main__') | |
236 _execfile(setup_script, ns) | |
237 DirectorySandbox(setup_dir).run(runner) | |
238 except SystemExit as v: | |
239 if v.args and v.args[0]: | |
240 raise | |
241 # Normal exit, just return | |
242 | |
243 | |
244 class AbstractSandbox: | |
245 """Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" | |
246 | |
247 _active = False | |
248 | |
249 def __init__(self): | |
250 self._attrs = [ | |
251 name for name in dir(_os) | |
252 if not name.startswith('_') and hasattr(self,name) | |
253 ] | |
254 | |
255 def _copy(self, source): | |
256 for name in self._attrs: | |
257 setattr(os, name, getattr(source,name)) | |
258 | |
259 def run(self, func): | |
260 """Run 'func' under os sandboxing""" | |
261 try: | |
262 self._copy(self) | |
263 if _file: | |
264 builtins.file = self._file | |
265 builtins.open = self._open | |
266 self._active = True | |
267 return func() | |
268 finally: | |
269 self._active = False | |
270 if _file: | |
271 builtins.file = _file | |
272 builtins.open = _open | |
273 self._copy(_os) | |
274 | |
275 def _mk_dual_path_wrapper(name): | |
276 original = getattr(_os,name) | |
277 def wrap(self,src,dst,*args,**kw): | |
278 if self._active: | |
279 src,dst = self._remap_pair(name,src,dst,*args,**kw) | |
280 return original(src,dst,*args,**kw) | |
281 return wrap | |
282 | |
283 for name in ["rename", "link", "symlink"]: | |
284 if hasattr(_os,name): locals()[name] = _mk_dual_path_wrapper(name) | |
285 | |
286 def _mk_single_path_wrapper(name, original=None): | |
287 original = original or getattr(_os,name) | |
288 def wrap(self,path,*args,**kw): | |
289 if self._active: | |
290 path = self._remap_input(name,path,*args,**kw) | |
291 return original(path,*args,**kw) | |
292 return wrap | |
293 | |
294 if _file: | |
295 _file = _mk_single_path_wrapper('file', _file) | |
296 _open = _mk_single_path_wrapper('open', _open) | |
297 for name in [ | |
298 "stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir", | |
299 "remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat", | |
300 "startfile", "mkfifo", "mknod", "pathconf", "access" | |
301 ]: | |
302 if hasattr(_os,name): locals()[name] = _mk_single_path_wrapper(name) | |
303 | |
304 def _mk_single_with_return(name): | |
305 original = getattr(_os,name) | |
306 def wrap(self,path,*args,**kw): | |
307 if self._active: | |
308 path = self._remap_input(name,path,*args,**kw) | |
309 return self._remap_output(name, original(path,*args,**kw)) | |
310 return original(path,*args,**kw) | |
311 return wrap | |
312 | |
313 for name in ['readlink', 'tempnam']: | |
314 if hasattr(_os,name): locals()[name] = _mk_single_with_return(name) | |
315 | |
316 def _mk_query(name): | |
317 original = getattr(_os,name) | |
318 def wrap(self,*args,**kw): | |
319 retval = original(*args,**kw) | |
320 if self._active: | |
321 return self._remap_output(name, retval) | |
322 return retval | |
323 return wrap | |
324 | |
325 for name in ['getcwd', 'tmpnam']: | |
326 if hasattr(_os,name): locals()[name] = _mk_query(name) | |
327 | |
328 def _validate_path(self,path): | |
329 """Called to remap or validate any path, whether input or output""" | |
330 return path | |
331 | |
332 def _remap_input(self,operation,path,*args,**kw): | |
333 """Called for path inputs""" | |
334 return self._validate_path(path) | |
335 | |
336 def _remap_output(self,operation,path): | |
337 """Called for path outputs""" | |
338 return self._validate_path(path) | |
339 | |
340 def _remap_pair(self,operation,src,dst,*args,**kw): | |
341 """Called for path pairs like rename, link, and symlink operations""" | |
342 return ( | |
343 self._remap_input(operation+'-from',src,*args,**kw), | |
344 self._remap_input(operation+'-to',dst,*args,**kw) | |
345 ) | |
346 | |
347 | |
348 if hasattr(os, 'devnull'): | |
349 _EXCEPTIONS = [os.devnull,] | |
350 else: | |
351 _EXCEPTIONS = [] | |
352 | |
353 try: | |
354 from win32com.client.gencache import GetGeneratePath | |
355 _EXCEPTIONS.append(GetGeneratePath()) | |
356 del GetGeneratePath | |
357 except ImportError: | |
358 # it appears pywin32 is not installed, so no need to exclude. | |
359 pass | |
360 | |
361 class DirectorySandbox(AbstractSandbox): | |
362 """Restrict operations to a single subdirectory - pseudo-chroot""" | |
363 | |
364 write_ops = dict.fromkeys([ | |
365 "open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir", | |
366 "utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam", | |
367 ]) | |
368 | |
369 _exception_patterns = [ | |
370 # Allow lib2to3 to attempt to save a pickled grammar object (#121) | |
371 '.*lib2to3.*\.pickle$', | |
372 ] | |
373 "exempt writing to paths that match the pattern" | |
374 | |
375 def __init__(self, sandbox, exceptions=_EXCEPTIONS): | |
376 self._sandbox = os.path.normcase(os.path.realpath(sandbox)) | |
377 self._prefix = os.path.join(self._sandbox,'') | |
378 self._exceptions = [ | |
379 os.path.normcase(os.path.realpath(path)) | |
380 for path in exceptions | |
381 ] | |
382 AbstractSandbox.__init__(self) | |
383 | |
384 def _violation(self, operation, *args, **kw): | |
385 raise SandboxViolation(operation, args, kw) | |
386 | |
387 if _file: | |
388 def _file(self, path, mode='r', *args, **kw): | |
389 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): | |
390 self._violation("file", path, mode, *args, **kw) | |
391 return _file(path,mode,*args,**kw) | |
392 | |
393 def _open(self, path, mode='r', *args, **kw): | |
394 if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): | |
395 self._violation("open", path, mode, *args, **kw) | |
396 return _open(path,mode,*args,**kw) | |
397 | |
398 def tmpnam(self): | |
399 self._violation("tmpnam") | |
400 | |
401 def _ok(self, path): | |
402 active = self._active | |
403 try: | |
404 self._active = False | |
405 realpath = os.path.normcase(os.path.realpath(path)) | |
406 return ( | |
407 self._exempted(realpath) | |
408 or realpath == self._sandbox | |
409 or realpath.startswith(self._prefix) | |
410 ) | |
411 finally: | |
412 self._active = active | |
413 | |
414 def _exempted(self, filepath): | |
415 start_matches = ( | |
416 filepath.startswith(exception) | |
417 for exception in self._exceptions | |
418 ) | |
419 pattern_matches = ( | |
420 re.match(pattern, filepath) | |
421 for pattern in self._exception_patterns | |
422 ) | |
423 candidates = itertools.chain(start_matches, pattern_matches) | |
424 return any(candidates) | |
425 | |
426 def _remap_input(self, operation, path, *args, **kw): | |
427 """Called for path inputs""" | |
428 if operation in self.write_ops and not self._ok(path): | |
429 self._violation(operation, os.path.realpath(path), *args, **kw) | |
430 return path | |
431 | |
432 def _remap_pair(self, operation, src, dst, *args, **kw): | |
433 """Called for path pairs like rename, link, and symlink operations""" | |
434 if not self._ok(src) or not self._ok(dst): | |
435 self._violation(operation, src, dst, *args, **kw) | |
436 return (src,dst) | |
437 | |
438 def open(self, file, flags, mode=0o777, *args, **kw): | |
439 """Called for low-level os.open()""" | |
440 if flags & WRITE_FLAGS and not self._ok(file): | |
441 self._violation("os.open", file, flags, mode, *args, **kw) | |
442 return _os.open(file,flags,mode, *args, **kw) | |
443 | |
444 WRITE_FLAGS = functools.reduce( | |
445 operator.or_, [getattr(_os, a, 0) for a in | |
446 "O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()] | |
447 ) | |
448 | |
449 class SandboxViolation(DistutilsError): | |
450 """A setup script attempted to modify the filesystem outside the sandbox""" | |
451 | |
452 def __str__(self): | |
453 return """SandboxViolation: %s%r %s | |
454 | |
455 The package setup script has attempted to modify files on your system | |
456 that are not within the EasyInstall build area, and has been aborted. | |
457 | |
458 This package cannot be safely installed by EasyInstall, and may not | |
459 support alternate installation locations even if you run its setup | |
460 script by hand. Please inform the package's author and the EasyInstall | |
461 maintainers to find out if a fix or workaround is available.""" % self.args | |
462 | |
463 | |
464 | |
465 | |
466 | |
467 | |
468 | |
469 | |
470 | |
471 | |
472 | |
473 | |
474 | |
475 | |
476 | |
477 | |
478 | |
479 | |
480 | |
481 | |
482 | |
483 | |
484 | |
485 | |
486 | |
487 | |
488 | |
489 # |