Made an application for supporting sustainable local businesses in San Pancho.
Never really got completed, but it has some useful Svelte components for maps that we can reuse.
http://greenspots.dctrl.space
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
491 lines
14 KiB
491 lines
14 KiB
import os |
|
import sys |
|
import tempfile |
|
import operator |
|
import functools |
|
import itertools |
|
import re |
|
import contextlib |
|
import pickle |
|
import textwrap |
|
|
|
from setuptools.extern import six |
|
from setuptools.extern.six.moves import builtins, map |
|
|
|
import pkg_resources.py31compat |
|
|
|
if sys.platform.startswith('java'): |
|
import org.python.modules.posix.PosixModule as _os |
|
else: |
|
_os = sys.modules[os.name] |
|
try: |
|
_file = file |
|
except NameError: |
|
_file = None |
|
_open = open |
|
from distutils.errors import DistutilsError |
|
from pkg_resources import working_set |
|
|
|
|
|
__all__ = [ |
|
"AbstractSandbox", "DirectorySandbox", "SandboxViolation", "run_setup", |
|
] |
|
|
|
|
|
def _execfile(filename, globals, locals=None): |
|
""" |
|
Python 3 implementation of execfile. |
|
""" |
|
mode = 'rb' |
|
with open(filename, mode) as stream: |
|
script = stream.read() |
|
if locals is None: |
|
locals = globals |
|
code = compile(script, filename, 'exec') |
|
exec(code, globals, locals) |
|
|
|
|
|
@contextlib.contextmanager |
|
def save_argv(repl=None): |
|
saved = sys.argv[:] |
|
if repl is not None: |
|
sys.argv[:] = repl |
|
try: |
|
yield saved |
|
finally: |
|
sys.argv[:] = saved |
|
|
|
|
|
@contextlib.contextmanager |
|
def save_path(): |
|
saved = sys.path[:] |
|
try: |
|
yield saved |
|
finally: |
|
sys.path[:] = saved |
|
|
|
|
|
@contextlib.contextmanager |
|
def override_temp(replacement): |
|
""" |
|
Monkey-patch tempfile.tempdir with replacement, ensuring it exists |
|
""" |
|
pkg_resources.py31compat.makedirs(replacement, exist_ok=True) |
|
|
|
saved = tempfile.tempdir |
|
|
|
tempfile.tempdir = replacement |
|
|
|
try: |
|
yield |
|
finally: |
|
tempfile.tempdir = saved |
|
|
|
|
|
@contextlib.contextmanager |
|
def pushd(target): |
|
saved = os.getcwd() |
|
os.chdir(target) |
|
try: |
|
yield saved |
|
finally: |
|
os.chdir(saved) |
|
|
|
|
|
class UnpickleableException(Exception): |
|
""" |
|
An exception representing another Exception that could not be pickled. |
|
""" |
|
|
|
@staticmethod |
|
def dump(type, exc): |
|
""" |
|
Always return a dumped (pickled) type and exc. If exc can't be pickled, |
|
wrap it in UnpickleableException first. |
|
""" |
|
try: |
|
return pickle.dumps(type), pickle.dumps(exc) |
|
except Exception: |
|
# get UnpickleableException inside the sandbox |
|
from setuptools.sandbox import UnpickleableException as cls |
|
return cls.dump(cls, cls(repr(exc))) |
|
|
|
|
|
class ExceptionSaver: |
|
""" |
|
A Context Manager that will save an exception, serialized, and restore it |
|
later. |
|
""" |
|
|
|
def __enter__(self): |
|
return self |
|
|
|
def __exit__(self, type, exc, tb): |
|
if not exc: |
|
return |
|
|
|
# dump the exception |
|
self._saved = UnpickleableException.dump(type, exc) |
|
self._tb = tb |
|
|
|
# suppress the exception |
|
return True |
|
|
|
def resume(self): |
|
"restore and re-raise any exception" |
|
|
|
if '_saved' not in vars(self): |
|
return |
|
|
|
type, exc = map(pickle.loads, self._saved) |
|
six.reraise(type, exc, self._tb) |
|
|
|
|
|
@contextlib.contextmanager |
|
def save_modules(): |
|
""" |
|
Context in which imported modules are saved. |
|
|
|
Translates exceptions internal to the context into the equivalent exception |
|
outside the context. |
|
""" |
|
saved = sys.modules.copy() |
|
with ExceptionSaver() as saved_exc: |
|
yield saved |
|
|
|
sys.modules.update(saved) |
|
# remove any modules imported since |
|
del_modules = ( |
|
mod_name for mod_name in sys.modules |
|
if mod_name not in saved |
|
# exclude any encodings modules. See #285 |
|
and not mod_name.startswith('encodings.') |
|
) |
|
_clear_modules(del_modules) |
|
|
|
saved_exc.resume() |
|
|
|
|
|
def _clear_modules(module_names): |
|
for mod_name in list(module_names): |
|
del sys.modules[mod_name] |
|
|
|
|
|
@contextlib.contextmanager |
|
def save_pkg_resources_state(): |
|
saved = pkg_resources.__getstate__() |
|
try: |
|
yield saved |
|
finally: |
|
pkg_resources.__setstate__(saved) |
|
|
|
|
|
@contextlib.contextmanager |
|
def setup_context(setup_dir): |
|
temp_dir = os.path.join(setup_dir, 'temp') |
|
with save_pkg_resources_state(): |
|
with save_modules(): |
|
hide_setuptools() |
|
with save_path(): |
|
with save_argv(): |
|
with override_temp(temp_dir): |
|
with pushd(setup_dir): |
|
# ensure setuptools commands are available |
|
__import__('setuptools') |
|
yield |
|
|
|
|
|
def _needs_hiding(mod_name): |
|
""" |
|
>>> _needs_hiding('setuptools') |
|
True |
|
>>> _needs_hiding('pkg_resources') |
|
True |
|
>>> _needs_hiding('setuptools_plugin') |
|
False |
|
>>> _needs_hiding('setuptools.__init__') |
|
True |
|
>>> _needs_hiding('distutils') |
|
True |
|
>>> _needs_hiding('os') |
|
False |
|
>>> _needs_hiding('Cython') |
|
True |
|
""" |
|
pattern = re.compile(r'(setuptools|pkg_resources|distutils|Cython)(\.|$)') |
|
return bool(pattern.match(mod_name)) |
|
|
|
|
|
def hide_setuptools(): |
|
""" |
|
Remove references to setuptools' modules from sys.modules to allow the |
|
invocation to import the most appropriate setuptools. This technique is |
|
necessary to avoid issues such as #315 where setuptools upgrading itself |
|
would fail to find a function declared in the metadata. |
|
""" |
|
modules = filter(_needs_hiding, sys.modules) |
|
_clear_modules(modules) |
|
|
|
|
|
def run_setup(setup_script, args): |
|
"""Run a distutils setup script, sandboxed in its directory""" |
|
setup_dir = os.path.abspath(os.path.dirname(setup_script)) |
|
with setup_context(setup_dir): |
|
try: |
|
sys.argv[:] = [setup_script] + list(args) |
|
sys.path.insert(0, setup_dir) |
|
# reset to include setup dir, w/clean callback list |
|
working_set.__init__() |
|
working_set.callbacks.append(lambda dist: dist.activate()) |
|
|
|
# __file__ should be a byte string on Python 2 (#712) |
|
dunder_file = ( |
|
setup_script |
|
if isinstance(setup_script, str) else |
|
setup_script.encode(sys.getfilesystemencoding()) |
|
) |
|
|
|
with DirectorySandbox(setup_dir): |
|
ns = dict(__file__=dunder_file, __name__='__main__') |
|
_execfile(setup_script, ns) |
|
except SystemExit as v: |
|
if v.args and v.args[0]: |
|
raise |
|
# Normal exit, just return |
|
|
|
|
|
class AbstractSandbox: |
|
"""Wrap 'os' module and 'open()' builtin for virtualizing setup scripts""" |
|
|
|
_active = False |
|
|
|
def __init__(self): |
|
self._attrs = [ |
|
name for name in dir(_os) |
|
if not name.startswith('_') and hasattr(self, name) |
|
] |
|
|
|
def _copy(self, source): |
|
for name in self._attrs: |
|
setattr(os, name, getattr(source, name)) |
|
|
|
def __enter__(self): |
|
self._copy(self) |
|
if _file: |
|
builtins.file = self._file |
|
builtins.open = self._open |
|
self._active = True |
|
|
|
def __exit__(self, exc_type, exc_value, traceback): |
|
self._active = False |
|
if _file: |
|
builtins.file = _file |
|
builtins.open = _open |
|
self._copy(_os) |
|
|
|
def run(self, func): |
|
"""Run 'func' under os sandboxing""" |
|
with self: |
|
return func() |
|
|
|
def _mk_dual_path_wrapper(name): |
|
original = getattr(_os, name) |
|
|
|
def wrap(self, src, dst, *args, **kw): |
|
if self._active: |
|
src, dst = self._remap_pair(name, src, dst, *args, **kw) |
|
return original(src, dst, *args, **kw) |
|
|
|
return wrap |
|
|
|
for name in ["rename", "link", "symlink"]: |
|
if hasattr(_os, name): |
|
locals()[name] = _mk_dual_path_wrapper(name) |
|
|
|
def _mk_single_path_wrapper(name, original=None): |
|
original = original or getattr(_os, name) |
|
|
|
def wrap(self, path, *args, **kw): |
|
if self._active: |
|
path = self._remap_input(name, path, *args, **kw) |
|
return original(path, *args, **kw) |
|
|
|
return wrap |
|
|
|
if _file: |
|
_file = _mk_single_path_wrapper('file', _file) |
|
_open = _mk_single_path_wrapper('open', _open) |
|
for name in [ |
|
"stat", "listdir", "chdir", "open", "chmod", "chown", "mkdir", |
|
"remove", "unlink", "rmdir", "utime", "lchown", "chroot", "lstat", |
|
"startfile", "mkfifo", "mknod", "pathconf", "access" |
|
]: |
|
if hasattr(_os, name): |
|
locals()[name] = _mk_single_path_wrapper(name) |
|
|
|
def _mk_single_with_return(name): |
|
original = getattr(_os, name) |
|
|
|
def wrap(self, path, *args, **kw): |
|
if self._active: |
|
path = self._remap_input(name, path, *args, **kw) |
|
return self._remap_output(name, original(path, *args, **kw)) |
|
return original(path, *args, **kw) |
|
|
|
return wrap |
|
|
|
for name in ['readlink', 'tempnam']: |
|
if hasattr(_os, name): |
|
locals()[name] = _mk_single_with_return(name) |
|
|
|
def _mk_query(name): |
|
original = getattr(_os, name) |
|
|
|
def wrap(self, *args, **kw): |
|
retval = original(*args, **kw) |
|
if self._active: |
|
return self._remap_output(name, retval) |
|
return retval |
|
|
|
return wrap |
|
|
|
for name in ['getcwd', 'tmpnam']: |
|
if hasattr(_os, name): |
|
locals()[name] = _mk_query(name) |
|
|
|
def _validate_path(self, path): |
|
"""Called to remap or validate any path, whether input or output""" |
|
return path |
|
|
|
def _remap_input(self, operation, path, *args, **kw): |
|
"""Called for path inputs""" |
|
return self._validate_path(path) |
|
|
|
def _remap_output(self, operation, path): |
|
"""Called for path outputs""" |
|
return self._validate_path(path) |
|
|
|
def _remap_pair(self, operation, src, dst, *args, **kw): |
|
"""Called for path pairs like rename, link, and symlink operations""" |
|
return ( |
|
self._remap_input(operation + '-from', src, *args, **kw), |
|
self._remap_input(operation + '-to', dst, *args, **kw) |
|
) |
|
|
|
|
|
if hasattr(os, 'devnull'): |
|
_EXCEPTIONS = [os.devnull,] |
|
else: |
|
_EXCEPTIONS = [] |
|
|
|
|
|
class DirectorySandbox(AbstractSandbox): |
|
"""Restrict operations to a single subdirectory - pseudo-chroot""" |
|
|
|
write_ops = dict.fromkeys([ |
|
"open", "chmod", "chown", "mkdir", "remove", "unlink", "rmdir", |
|
"utime", "lchown", "chroot", "mkfifo", "mknod", "tempnam", |
|
]) |
|
|
|
_exception_patterns = [ |
|
# Allow lib2to3 to attempt to save a pickled grammar object (#121) |
|
r'.*lib2to3.*\.pickle$', |
|
] |
|
"exempt writing to paths that match the pattern" |
|
|
|
def __init__(self, sandbox, exceptions=_EXCEPTIONS): |
|
self._sandbox = os.path.normcase(os.path.realpath(sandbox)) |
|
self._prefix = os.path.join(self._sandbox, '') |
|
self._exceptions = [ |
|
os.path.normcase(os.path.realpath(path)) |
|
for path in exceptions |
|
] |
|
AbstractSandbox.__init__(self) |
|
|
|
def _violation(self, operation, *args, **kw): |
|
from setuptools.sandbox import SandboxViolation |
|
raise SandboxViolation(operation, args, kw) |
|
|
|
if _file: |
|
|
|
def _file(self, path, mode='r', *args, **kw): |
|
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): |
|
self._violation("file", path, mode, *args, **kw) |
|
return _file(path, mode, *args, **kw) |
|
|
|
def _open(self, path, mode='r', *args, **kw): |
|
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path): |
|
self._violation("open", path, mode, *args, **kw) |
|
return _open(path, mode, *args, **kw) |
|
|
|
def tmpnam(self): |
|
self._violation("tmpnam") |
|
|
|
def _ok(self, path): |
|
active = self._active |
|
try: |
|
self._active = False |
|
realpath = os.path.normcase(os.path.realpath(path)) |
|
return ( |
|
self._exempted(realpath) |
|
or realpath == self._sandbox |
|
or realpath.startswith(self._prefix) |
|
) |
|
finally: |
|
self._active = active |
|
|
|
def _exempted(self, filepath): |
|
start_matches = ( |
|
filepath.startswith(exception) |
|
for exception in self._exceptions |
|
) |
|
pattern_matches = ( |
|
re.match(pattern, filepath) |
|
for pattern in self._exception_patterns |
|
) |
|
candidates = itertools.chain(start_matches, pattern_matches) |
|
return any(candidates) |
|
|
|
def _remap_input(self, operation, path, *args, **kw): |
|
"""Called for path inputs""" |
|
if operation in self.write_ops and not self._ok(path): |
|
self._violation(operation, os.path.realpath(path), *args, **kw) |
|
return path |
|
|
|
def _remap_pair(self, operation, src, dst, *args, **kw): |
|
"""Called for path pairs like rename, link, and symlink operations""" |
|
if not self._ok(src) or not self._ok(dst): |
|
self._violation(operation, src, dst, *args, **kw) |
|
return (src, dst) |
|
|
|
def open(self, file, flags, mode=0o777, *args, **kw): |
|
"""Called for low-level os.open()""" |
|
if flags & WRITE_FLAGS and not self._ok(file): |
|
self._violation("os.open", file, flags, mode, *args, **kw) |
|
return _os.open(file, flags, mode, *args, **kw) |
|
|
|
|
|
WRITE_FLAGS = functools.reduce( |
|
operator.or_, [getattr(_os, a, 0) for a in |
|
"O_WRONLY O_RDWR O_APPEND O_CREAT O_TRUNC O_TEMPORARY".split()] |
|
) |
|
|
|
|
|
class SandboxViolation(DistutilsError): |
|
"""A setup script attempted to modify the filesystem outside the sandbox""" |
|
|
|
tmpl = textwrap.dedent(""" |
|
SandboxViolation: {cmd}{args!r} {kwargs} |
|
|
|
The package setup script has attempted to modify files on your system |
|
that are not within the EasyInstall build area, and has been aborted. |
|
|
|
This package cannot be safely installed by EasyInstall, and may not |
|
support alternate installation locations even if you run its setup |
|
script by hand. Please inform the package's author and the EasyInstall |
|
maintainers to find out if a fix or workaround is available. |
|
""").lstrip() |
|
|
|
def __str__(self): |
|
cmd, args, kwargs = self.args |
|
return self.tmpl.format(**locals())
|
|
|