|
1 | 1 | """Zipfile entry point which supports auto-extracting itself based on zip-safety."""
|
2 | 2 |
|
3 |
| -from collections import defaultdict |
4 |
| -from importlib import import_module, machinery |
5 |
| -from importlib.abc import MetaPathFinder |
6 |
| -from importlib.metadata import Distribution |
7 |
| -from importlib.util import spec_from_loader |
8 |
| -from site import getsitepackages |
9 |
| -import itertools |
10 | 3 | import os
|
11 |
| -import re |
12 | 4 | import runpy
|
13 | 5 | import sys
|
14 |
| -import tempfile |
15 |
| -import zipfile |
16 | 6 |
|
17 | 7 | # Put this pex on the path before anything else.
|
18 | 8 | PEX = os.path.abspath(sys.argv[0])
|
|
26 | 16 | ZIP_SAFE = __ZIP_SAFE__
|
27 | 17 | PEX_STAMP = '__PEX_STAMP__'
|
28 | 18 |
|
29 |
| -# Workaround for https://bugs.python.org/issue15795 |
30 |
| -class ZipFileWithPermissions(zipfile.ZipFile): |
31 |
| - """ Custom ZipFile class handling file permissions. """ |
32 |
| - |
33 |
| - def _extract_member(self, member, targetpath, pwd): |
34 |
| - if not isinstance(member, zipfile.ZipInfo): |
35 |
| - member = self.getinfo(member) |
36 |
| - |
37 |
| - targetpath = super(ZipFileWithPermissions, self)._extract_member( |
38 |
| - member, targetpath, pwd |
39 |
| - ) |
40 |
| - |
41 |
| - attr = member.external_attr >> 16 |
42 |
| - if attr != 0: |
43 |
| - os.chmod(targetpath, attr) |
44 |
| - return targetpath |
45 |
| - |
46 |
| -class SoImport(MetaPathFinder): |
47 |
| - """So import. Much binary. Such dynamic. Wow.""" |
48 |
| - |
49 |
| - def __init__(self): |
50 |
| - self.suffixes = machinery.EXTENSION_SUFFIXES # list, as importlib will not be using the file description |
51 |
| - self.suffixes_by_length = sorted(self.suffixes, key=lambda x: -len(x)) |
52 |
| - # Identify all the possible modules we could handle. |
53 |
| - self.modules = {} |
54 |
| - if zipfile.is_zipfile(sys.argv[0]): |
55 |
| - zf = ZipFileWithPermissions(sys.argv[0]) |
56 |
| - for name in zf.namelist(): |
57 |
| - path, _ = self.splitext(name) |
58 |
| - if path: |
59 |
| - if path.startswith('.bootstrap/'): |
60 |
| - path = path[len('.bootstrap/'):] |
61 |
| - importpath = path.replace('/', '.') |
62 |
| - self.modules.setdefault(importpath, name) |
63 |
| - if path.startswith(MODULE_DIR): |
64 |
| - self.modules.setdefault(importpath[len(MODULE_DIR)+1:], name) |
65 |
| - if self.modules: |
66 |
| - self.zf = zf |
67 |
| - |
68 |
| - def find_spec(self, name, path, target=None): |
69 |
| - """Implements abc.MetaPathFinder.""" |
70 |
| - if name in self.modules: |
71 |
| - return spec_from_loader(name, self) |
72 |
| - |
73 |
| - def create_module(self, spec): |
74 |
| - """Create a module object that we're going to load.""" |
75 |
| - filename = self.modules[spec.name] |
76 |
| - prefix, ext = self.splitext(filename) |
77 |
| - with tempfile.NamedTemporaryFile(suffix=ext, prefix=os.path.basename(prefix)) as f: |
78 |
| - f.write(self.zf.read(filename)) |
79 |
| - f.flush() |
80 |
| - spec.origin = f.name |
81 |
| - loader = machinery.ExtensionFileLoader(spec.name, f.name) |
82 |
| - spec.loader = loader |
83 |
| - mod = loader.create_module(spec) |
84 |
| - # Make it look like module came from the original location for nicer tracebacks. |
85 |
| - mod.__file__ = filename |
86 |
| - return mod |
87 |
| - |
88 |
| - def exec_module(self, mod): |
89 |
| - """Because we set spec.loader above, the ExtensionFileLoader's exec_module is called.""" |
90 |
| - raise NotImplementedError("SoImport.exec_module isn't used") |
91 |
| - |
92 |
| - def splitext(self, path): |
93 |
| - """Similar to os.path.splitext, but splits our longest known suffix preferentially.""" |
94 |
| - for suffix in self.suffixes_by_length: |
95 |
| - if path.endswith(suffix): |
96 |
| - return path[:-len(suffix)], suffix |
97 |
| - return None, None |
98 |
| - |
99 |
| - |
100 |
| -class PexDistribution(Distribution): |
101 |
| - """Represents a distribution package that exists within a pex file (which is, ultimately, a zip |
102 |
| - file). Distribution packages are identified by the presence of a suitable dist-info or egg-info |
103 |
| - directory member inside the pex file, which need not necessarily exist at the top level if a |
104 |
| - directory prefix is specified in the constructor. |
105 |
| - """ |
106 |
| - def __init__(self, name, pex_file, zip_file, files, prefix): |
107 |
| - self._name = name |
108 |
| - self._zf = zip_file |
109 |
| - self._pex_file = pex_file |
110 |
| - self._prefix = prefix |
111 |
| - # Mapping of <path within distribution> -> <full path in zipfile> |
112 |
| - self._files = files |
113 |
| - |
114 |
| - def read_text(self, filename): |
115 |
| - full_name = self._files.get(filename) |
116 |
| - if full_name: |
117 |
| - return self._zf.read(full_name).decode(encoding="utf-8") |
118 |
| - |
119 |
| - def locate_file(self, path): |
120 |
| - return zipfile.Path( |
121 |
| - self._pex_file, |
122 |
| - at=os.path.join(self._prefix, path) if self._prefix else path, |
123 |
| - ) |
124 |
| - |
125 |
| - read_text.__doc__ = Distribution.read_text.__doc__ |
126 |
| - |
127 |
| - |
128 |
| -class ModuleDirImport(MetaPathFinder): |
129 |
| - """Handles imports to a directory equivalently to them being at the top level. |
130 |
| -
|
131 |
| - This means that if one writes `import third_party.python.six`, it's imported like `import six`, |
132 |
| - but becomes accessible under both names. This handles both the fully-qualified import names |
133 |
| - and packages importing as their expected top-level names internally. |
134 |
| - """ |
135 |
| - def __init__(self, module_dir=MODULE_DIR): |
136 |
| - self.prefix = module_dir.replace("/", ".") + "." |
137 |
| - self._distributions = self._find_all_distributions(module_dir) |
138 |
| - |
139 |
| - def _find_all_distributions(self, module_dir): |
140 |
| - pex_file = sys.argv[0] |
141 |
| - if zipfile.is_zipfile(pex_file): |
142 |
| - zf = ZipFileWithPermissions(pex_file) |
143 |
| - r = re.compile(r"{module_dir}{sep}([^/]+)-[^/-]+?\.(?:dist|egg)-info/(.*)".format( |
144 |
| - module_dir=module_dir, |
145 |
| - sep = os.sep, |
146 |
| - )) |
147 |
| - filenames = defaultdict(dict) |
148 |
| - for name in zf.namelist(): |
149 |
| - match = r.match(name) |
150 |
| - if match: |
151 |
| - filenames[match.group(1)][match.group(2)] = name |
152 |
| - return {mod: [PexDistribution(mod, pex_file, zf, files, prefix=module_dir)] |
153 |
| - for mod, files in filenames.items()} |
154 |
| - return {} |
155 |
| - |
156 |
| - def find_spec(self, name, path, target=None): |
157 |
| - """Implements abc.MetaPathFinder.""" |
158 |
| - if name.startswith(self.prefix): |
159 |
| - return spec_from_loader(name, self) |
160 |
| - |
161 |
| - def create_module(self, spec): |
162 |
| - """Actually load a module that we said we'd handle in find_module.""" |
163 |
| - module = import_module(spec.name.removeprefix(self.prefix)) |
164 |
| - sys.modules[spec.name] = module |
165 |
| - return module |
166 |
| - |
167 |
| - def exec_module(self, mod): |
168 |
| - """Nothing to do, create_module already did the work.""" |
169 |
| - |
170 |
| - def find_distributions(self, context): |
171 |
| - """Return an iterable of all Distribution instances capable of |
172 |
| - loading the metadata for packages for the indicated ``context``. |
173 |
| - """ |
174 |
| - if context.name: |
175 |
| - # The installed directories have underscores in the place of what might be a hyphen |
176 |
| - # in the package name (e.g. the package opentelemetry-sdk installs opentelemetry_sdk). |
177 |
| - return self._distributions.get(context.name.replace("-", "_"), []) |
178 |
| - else: |
179 |
| - return itertools.chain(*self._distributions.values()) |
180 |
| - |
181 |
| - def get_code(self, fullname): |
182 |
| - module = import_module(fullname.removeprefix(self.prefix)) |
183 |
| - return module.__loader__.get_code(fullname) |
184 |
| - |
185 |
| - |
186 |
| -def add_module_dir_to_sys_path(dirname): |
| 19 | + |
| 20 | +def add_module_dir_to_sys_path(dirname, zip_safe=True): |
187 | 21 | """Adds the given dirname to sys.path if it's nonempty."""
|
| 22 | + # Add .bootstrap dir to path, after the initial pex entry |
| 23 | + sys.path = sys.path[:1] + [os.path.join(sys.path[0], '.bootstrap')] + sys.path[1:] |
| 24 | + # Now we have .bootstrap on the path, we can import our own hooks. |
| 25 | + import plz |
188 | 26 | if dirname:
|
189 | 27 | sys.path = sys.path[:1] + [os.path.join(sys.path[0], dirname)] + sys.path[1:]
|
190 |
| - sys.meta_path.insert(0, ModuleDirImport(dirname)) |
| 28 | + sys.meta_path.insert(0, plz.ModuleDirImport(dirname)) |
| 29 | + if zip_safe: |
| 30 | + sys.meta_path.append(plz.SoImport(MODULE_DIR)) |
191 | 31 |
|
192 | 32 |
|
193 | 33 | def pex_basepath(temp=False):
|
@@ -297,8 +137,6 @@ def main():
|
297 | 137 |
|
298 | 138 | N.B. This gets redefined by pex_test_main to run tests instead.
|
299 | 139 | """
|
300 |
| - # Add .bootstrap dir to path, after the initial pex entry |
301 |
| - sys.path = sys.path[:1] + [os.path.join(sys.path[0], '.bootstrap')] + sys.path[1:] |
302 | 140 | # Starts a debugging session, if defined, before running the entry point.
|
303 | 141 | if os.getenv("PLZ_DEBUG") is not None:
|
304 | 142 | start_debugger()
|
|
0 commit comments