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 #