1
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
4
from __future__ import absolute_import, print_function
10
from collections import namedtuple
12
from .common import open_zip
13
from .compatibility import string as compatibility_string
14
from .orderedset import OrderedSet
16
PexPlatform = namedtuple('PexPlatform', 'interpreter version strict')
19
class PexInfo(object):
23
build_properties: BuildProperties # (key-value information about the build system)
24
code_hash: str # sha1 hash of all names/code in the archive
25
distributions: {dist_name: str} # map from distribution name (i.e. path in
26
# the internal cache) to its cache key (sha1)
29
pex_root: ~/.pex # root of all pex-related files
30
entry_point: string # entry point into this pex
31
zip_safe: True, default False # is this pex zip safe?
32
inherit_path: True, default False # should this pex inherit site-packages + PYTHONPATH?
33
ignore_errors: True, default False # should we ignore inability to resolve dependencies?
34
always_write_cache: False # should we always write the internal cache to disk first?
35
# this is useful if you have very large dependencies that
36
# do not fit in RAM constrained environments
37
requirements: list # list of requirements for this environment
40
.. versionchanged:: 0.8
41
Removed the ``repositories`` and ``indices`` information, as they were never
46
INTERNAL_CACHE = '.deps'
49
def make_build_properties(cls):
50
from .interpreter import PythonInterpreter
51
from pkg_resources import get_platform
53
pi = PythonInterpreter.get()
55
'class': pi.identity.interpreter,
56
'version': pi.identity.version,
57
'platform': get_platform(),
65
'always_write_cache': False,
66
'build_properties': cls.make_build_properties(),
68
return cls(info=pex_info)
71
def from_pex(cls, pex):
72
if os.path.isfile(pex):
73
with open_zip(pex) as zf:
74
pex_info = zf.read(cls.PATH)
76
with open(os.path.join(pex, cls.PATH)) as fp:
78
return cls.from_json(pex_info)
81
def from_json(cls, content):
82
if isinstance(content, bytes):
83
content = content.decode('utf-8')
84
return PexInfo(info=json.loads(content))
87
def _parse_requirement_tuple(cls, requirement_tuple):
88
if isinstance(requirement_tuple, (tuple, list)):
89
if len(requirement_tuple) != 3:
90
raise ValueError('Malformed PEX requirement: %r' % (requirement_tuple,))
91
# pre 0.8.x requirement type:
92
warnings.warn('Attempting to use deprecated PEX feature. Please upgrade past PEX 0.8.x.')
93
return requirement_tuple[0]
94
elif isinstance(requirement_tuple, compatibility_string):
95
return requirement_tuple
96
raise ValueError('Malformed PEX requirement: %r' % (requirement_tuple,))
100
if 'PEX_VERBOSE' in os.environ:
101
print('PEX: %s' % msg, file=sys.stderr)
103
def __init__(self, info=None):
104
"""Construct a new PexInfo. This should not be used directly."""
106
if info is not None and not isinstance(info, dict):
107
raise ValueError('PexInfo can only be seeded with a dict, got: '
108
'%s of type %s' % (info, type(info)))
109
self._pex_info = info or {}
110
self._distributions = self._pex_info.get('distributions', {})
111
requirements = self._pex_info.get('requirements', [])
112
if not isinstance(requirements, (list, tuple)):
113
raise ValueError('Expected requirements to be a list, got %s' % type(requirements))
114
self._requirements = OrderedSet(self._parse_requirement_tuple(req) for req in requirements)
117
def build_properties(self):
118
"""Information about the system on which this PEX was generated.
120
:returns: A dictionary containing metadata about the environment used to build this PEX.
122
return self._pex_info.get('build_properties', {})
124
@build_properties.setter
125
def build_properties(self, value):
126
if not isinstance(value, dict):
127
raise TypeError('build_properties must be a dictionary!')
128
self._pex_info['build_properties'] = self.make_build_properties()
129
self._pex_info['build_properties'].update(value)
133
"""Whether or not this PEX should be treated as zip-safe.
135
If set to false and the PEX is zipped, the contents of the PEX will be unpacked into a
136
directory within the PEX_ROOT prior to execution. This allows code and frameworks depending
137
upon __file__ existing on disk to operate normally.
139
By default zip_safe is True. May be overridden at runtime by the $PEX_FORCE_LOCAL environment
142
if 'PEX_FORCE_LOCAL' in os.environ:
143
self.debug('PEX_FORCE_LOCAL forcing zip_safe to False')
145
return self._pex_info.get('zip_safe', True)
148
def zip_safe(self, value):
149
self._pex_info['zip_safe'] = bool(value)
152
def inherit_path(self):
153
"""Whether or not this PEX should be allowed to inherit system dependencies.
155
By default, PEX environments are scrubbed of all system distributions prior to execution.
156
This means that PEX files cannot rely upon preexisting system libraries.
158
By default inherit_path is False. This may be overridden at runtime by the $PEX_INHERIT_PATH
159
environment variable.
161
if 'PEX_INHERIT_PATH' in os.environ:
162
self.debug('PEX_INHERIT_PATH override detected')
165
return self._pex_info.get('inherit_path', False)
168
def inherit_path(self, value):
169
self._pex_info['inherit_path'] = bool(value)
172
def ignore_errors(self):
173
return self._pex_info.get('ignore_errors', False)
175
@ignore_errors.setter
176
def ignore_errors(self, value):
177
self._pex_info['ignore_errors'] = bool(value)
181
return self._pex_info.get('code_hash')
184
def code_hash(self, value):
185
self._pex_info['code_hash'] = value
188
def entry_point(self):
189
if 'PEX_MODULE' in os.environ:
190
self.debug('PEX_MODULE override detected: %s' % os.environ['PEX_MODULE'])
191
return os.environ['PEX_MODULE']
192
return self._pex_info.get('entry_point')
195
def entry_point(self, value):
196
self._pex_info['entry_point'] = value
198
def add_requirement(self, requirement):
199
self._requirements.add(str(requirement))
202
def requirements(self):
203
return self._requirements
205
def add_distribution(self, location, sha):
206
self._distributions[location] = sha
209
def distributions(self):
210
return self._distributions
213
def always_write_cache(self):
214
if 'PEX_ALWAYS_CACHE' in os.environ:
215
self.debug('PEX_ALWAYS_CACHE override detected: %s' % os.environ['PEX_ALWAYS_CACHE'])
217
return self._pex_info.get('always_write_cache', False)
219
@always_write_cache.setter
220
def always_write_cache(self, value):
221
self._pex_info['always_write_cache'] = bool(value)
225
pex_root = self._pex_info.get('pex_root', os.path.join('~', '.pex'))
226
return os.path.expanduser(os.environ.get('PEX_ROOT', pex_root))
229
def pex_root(self, value):
230
self._pex_info['pex_root'] = value
233
def internal_cache(self):
234
return self.INTERNAL_CACHE
237
def install_cache(self):
238
return os.path.join(self.pex_root, 'install')
241
def zip_unsafe_cache(self):
242
return os.path.join(self.pex_root, 'code')
245
pex_info_copy = self._pex_info.copy()
246
pex_info_copy['requirements'] = list(self._requirements)
247
return json.dumps(pex_info_copy)
250
return PexInfo(info=self._pex_info.copy())