We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Python 2: SyntaxError when installing distributions with coding hints in setup.py
I think this is a regression introduced in #231, which was released in pex 1.1.7.  I cannot reproduce this problem on pex 1.1.6.  Affects Python 2 only.

Reproduce with:

```
wget -q 'https://github.com/pantsbuild/pex/releases/download/v1.1.7/pex27'
chmod a+x pex27
./pex27 backoff
```

Stack trace:

```
**** Failed to install backoff-1.1.0. stdout:

**** Failed to install backoff-1.1.0. stderr:
Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
  File "setup.py", line 0
SyntaxError: encoding declaration in Unicode string

Traceback (most recent call last):
  File "/private/tmp/pex/pex27/.bootstrap/_pex/pex.py", line 328, in execute
  File "/private/tmp/pex/pex27/.bootstrap/_pex/pex.py", line 260, in _wrap_coverage
  File "/private/tmp/pex/pex27/.bootstrap/_pex/pex.py", line 292, in _wrap_profiling
  File "/private/tmp/pex/pex27/.bootstrap/_pex/pex.py", line 371, in _execute
  File "/private/tmp/pex/pex27/.bootstrap/_pex/pex.py", line 429, in execute_entry
  File "/private/tmp/pex/pex27/.bootstrap/_pex/pex.py", line 447, in execute_pkg_resources
  File "/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/bin/pex.py", line 538, in main
    pex_builder = build_pex(reqs, options, resolver_options_builder)
  File "/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/bin/pex.py", line 489, in build_pex
    resolveds = resolver.resolve(resolvables)
  File "/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/resolver.py", line 200, in resolve
    dist = self.build(package, resolvable.options)
  File "/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/resolver.py", line 257, in build
    dist = super(CachingResolver, self).build(package, options)
  File "/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/resolver.py", line 168, in build
    raise Untranslateable('Package %s is not translateable by %s' % (package, translator))
pex.resolver.Untranslateable: Package SourcePackage(u'file:///Users/saj/.pex/build/backoff-1.1.0.tar.gz') is not translateable by ChainedTranslator(WheelTranslator, EggTranslator, SourceTranslator)
```

Even if we were to ignore the problem above, I don't think it is right to assume a UTF-8 encoding on all files.

From [PEP 263](https://www.python.org/dev/peps/pep-0263/):

> If a Unicode string with a coding declaration is passed to compile(), a SyntaxError will be raised.

From the same PEP:

> The builtin compile() API will be enhanced to accept Unicode as input. 8-bit string input is subject to the standard procedure for encoding detection as described above.

Instead of opening `setup.py` as a text file and decoding it ourselves, it sounds like it should be possible to hand a binary stream directly to the `compile()` builtin and let Python deal with this muck.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pex/installer.py`
Content:
```
1 # Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
2 # Licensed under the Apache License, Version 2.0 (see LICENSE).
3 
4 from __future__ import absolute_import, print_function
5 
6 import os
7 import subprocess
8 import sys
9 import tempfile
10 
11 from pkg_resources import Distribution, PathMetadata
12 
13 from .common import safe_mkdtemp, safe_rmtree
14 from .compatibility import WINDOWS
15 from .interpreter import PythonInterpreter
16 from .tracer import TRACER
17 from .version import SETUPTOOLS_REQUIREMENT, WHEEL_REQUIREMENT
18 
19 __all__ = (
20   'Installer',
21   'Packager'
22 )
23 
24 
25 def after_installation(function):
26   def function_wrapper(self, *args, **kw):
27     self._installed = self.run()
28     if not self._installed:
29       raise Installer.InstallFailure('Failed to install %s' % self._source_dir)
30     return function(self, *args, **kw)
31   return function_wrapper
32 
33 
34 class InstallerBase(object):
35   SETUP_BOOTSTRAP_HEADER = "import io, sys"
36   SETUP_BOOTSTRAP_MODULE = "sys.path.insert(0, %(path)r); import %(module)s"
37   SETUP_BOOTSTRAP_FOOTER = """
38 __file__ = 'setup.py'
39 sys.argv[0] = 'setup.py'
40 exec(compile(io.open(__file__, encoding='utf-8').read().replace('\\r\\n', '\\n'), __file__, 'exec'))
41 """
42 
43   class Error(Exception): pass
44   class InstallFailure(Error): pass
45   class IncapableInterpreter(Error): pass
46 
47   def __init__(self, source_dir, strict=True, interpreter=None, install_dir=None):
48     """
49       Create an installer from an unpacked source distribution in source_dir.
50 
51       If strict=True, fail if any installation dependencies (e.g. distribute)
52       are missing.
53     """
54     self._source_dir = source_dir
55     self._install_tmp = install_dir or safe_mkdtemp()
56     self._installed = None
57     self._strict = strict
58     self._interpreter = interpreter or PythonInterpreter.get()
59     if not self._interpreter.satisfies(self.capability) and strict:
60       raise self.IncapableInterpreter('Interpreter %s not capable of running %s' % (
61           self._interpreter.binary, self.__class__.__name__))
62 
63   def mixins(self):
64     """Return a map from import name to requirement to load into setup script prior to invocation.
65 
66        May be subclassed.
67     """
68     return {}
69 
70   @property
71   def install_tmp(self):
72     return self._install_tmp
73 
74   def _setup_command(self):
75     """the setup command-line to run, to be implemented by subclasses."""
76     raise NotImplementedError
77 
78   def _postprocess(self):
79     """a post-processing function to run following setup.py invocation."""
80 
81   @property
82   def capability(self):
83     """returns the list of requirements for the interpreter to run this installer."""
84     return list(self.mixins().values())
85 
86   @property
87   def bootstrap_script(self):
88     bootstrap_modules = []
89     for module, requirement in self.mixins().items():
90       path = self._interpreter.get_location(requirement)
91       if not path:
92         assert not self._strict  # This should be caught by validation
93         continue
94       bootstrap_modules.append(self.SETUP_BOOTSTRAP_MODULE % {'path': path, 'module': module})
95     return '\n'.join(
96         [self.SETUP_BOOTSTRAP_HEADER] + bootstrap_modules + [self.SETUP_BOOTSTRAP_FOOTER])
97 
98   def run(self):
99     if self._installed is not None:
100       return self._installed
101 
102     with TRACER.timed('Installing %s' % self._install_tmp, V=2):
103       command = [self._interpreter.binary, '-']
104       command.extend(self._setup_command())
105       po = subprocess.Popen(command,
106           stdin=subprocess.PIPE,
107           stdout=subprocess.PIPE,
108           stderr=subprocess.PIPE,
109           env=self._interpreter.sanitized_environment(),
110           cwd=self._source_dir)
111       so, se = po.communicate(self.bootstrap_script.encode('ascii'))
112       self._installed = po.returncode == 0
113 
114     if not self._installed:
115       name = os.path.basename(self._source_dir)
116       print('**** Failed to install %s. stdout:\n%s' % (name, so.decode('utf-8')), file=sys.stderr)
117       print('**** Failed to install %s. stderr:\n%s' % (name, se.decode('utf-8')), file=sys.stderr)
118       return self._installed
119 
120     self._postprocess()
121     return self._installed
122 
123   def cleanup(self):
124     safe_rmtree(self._install_tmp)
125 
126 
127 class Installer(InstallerBase):
128   """Install an unpacked distribution with a setup.py."""
129 
130   def __init__(self, source_dir, strict=True, interpreter=None):
131     """
132       Create an installer from an unpacked source distribution in source_dir.
133 
134       If strict=True, fail if any installation dependencies (e.g. setuptools)
135       are missing.
136     """
137     super(Installer, self).__init__(source_dir, strict=strict, interpreter=interpreter)
138     self._egg_info = None
139     fd, self._install_record = tempfile.mkstemp()
140     os.close(fd)
141 
142   def _setup_command(self):
143     return ['install',
144            '--root=%s' % self._install_tmp,
145            '--prefix=',
146            '--single-version-externally-managed',
147            '--record', self._install_record]
148 
149   def _postprocess(self):
150     installed_files = []
151     egg_info = None
152     with open(self._install_record) as fp:
153       installed_files = fp.read().splitlines()
154       for line in installed_files:
155         if line.endswith('.egg-info'):
156           assert line.startswith('/'), 'Expect .egg-info to be within install_tmp!'
157           egg_info = line
158           break
159 
160     if not egg_info:
161       self._installed = False
162       return self._installed
163 
164     installed_files = [os.path.relpath(fn, egg_info) for fn in installed_files if fn != egg_info]
165 
166     self._egg_info = os.path.join(self._install_tmp, egg_info[1:])
167     with open(os.path.join(self._egg_info, 'installed-files.txt'), 'w') as fp:
168       fp.write('\n'.join(installed_files))
169       fp.write('\n')
170 
171     return self._installed
172 
173   @after_installation
174   def egg_info(self):
175     return self._egg_info
176 
177   @after_installation
178   def root(self):
179     egg_info = self.egg_info()
180     assert egg_info
181     return os.path.realpath(os.path.dirname(egg_info))
182 
183   @after_installation
184   def distribution(self):
185     base_dir = self.root()
186     egg_info = self.egg_info()
187     metadata = PathMetadata(base_dir, egg_info)
188     return Distribution.from_location(base_dir, os.path.basename(egg_info), metadata=metadata)
189 
190 
191 class DistributionPackager(InstallerBase):
192   def mixins(self):
193     mixins = super(DistributionPackager, self).mixins().copy()
194     mixins.update(setuptools=SETUPTOOLS_REQUIREMENT)
195     return mixins
196 
197   def find_distribution(self):
198     dists = os.listdir(self.install_tmp)
199     if len(dists) == 0:
200       raise self.InstallFailure('No distributions were produced!')
201     elif len(dists) > 1:
202       raise self.InstallFailure('Ambiguous source distributions found: %s' % (' '.join(dists)))
203     else:
204       return os.path.join(self.install_tmp, dists[0])
205 
206 
207 class Packager(DistributionPackager):
208   """
209     Create a source distribution from an unpacked setup.py-based project.
210   """
211 
212   def _setup_command(self):
213     if WINDOWS:
214       return ['sdist', '--formats=zip', '--dist-dir=%s' % self._install_tmp]
215     else:
216       return ['sdist', '--formats=gztar', '--dist-dir=%s' % self._install_tmp]
217 
218   @after_installation
219   def sdist(self):
220     return self.find_distribution()
221 
222 
223 class EggInstaller(DistributionPackager):
224   """
225     Create a source distribution from an unpacked setup.py-based project.
226   """
227 
228   def _setup_command(self):
229     return ['bdist_egg', '--dist-dir=%s' % self._install_tmp]
230 
231   @after_installation
232   def bdist(self):
233     return self.find_distribution()
234 
235 
236 class WheelInstaller(DistributionPackager):
237   """
238     Create a source distribution from an unpacked setup.py-based project.
239   """
240   MIXINS = {
241       'setuptools': SETUPTOOLS_REQUIREMENT,
242       'wheel': WHEEL_REQUIREMENT,
243   }
244 
245   def mixins(self):
246     mixins = super(WheelInstaller, self).mixins().copy()
247     mixins.update(self.MIXINS)
248     return mixins
249 
250   def _setup_command(self):
251     return ['bdist_wheel', '--dist-dir=%s' % self._install_tmp]
252 
253   @after_installation
254   def bdist(self):
255     return self.find_distribution()
256
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pex/installer.py b/pex/installer.py
--- a/pex/installer.py
+++ b/pex/installer.py
@@ -32,12 +32,12 @@
 
 
 class InstallerBase(object):
-  SETUP_BOOTSTRAP_HEADER = "import io, sys"
+  SETUP_BOOTSTRAP_HEADER = "import sys"
   SETUP_BOOTSTRAP_MODULE = "sys.path.insert(0, %(path)r); import %(module)s"
   SETUP_BOOTSTRAP_FOOTER = """
 __file__ = 'setup.py'
 sys.argv[0] = 'setup.py'
-exec(compile(io.open(__file__, encoding='utf-8').read().replace('\\r\\n', '\\n'), __file__, 'exec'))
+exec(compile(open(__file__, 'rb').read(), __file__, 'exec'))
 """
 
   class Error(Exception): pass

{"golden_diff": "diff --git a/pex/installer.py b/pex/installer.py\n--- a/pex/installer.py\n+++ b/pex/installer.py\n@@ -32,12 +32,12 @@\n \n \n class InstallerBase(object):\n-  SETUP_BOOTSTRAP_HEADER = \"import io, sys\"\n+  SETUP_BOOTSTRAP_HEADER = \"import sys\"\n   SETUP_BOOTSTRAP_MODULE = \"sys.path.insert(0, %(path)r); import %(module)s\"\n   SETUP_BOOTSTRAP_FOOTER = \"\"\"\n __file__ = 'setup.py'\n sys.argv[0] = 'setup.py'\n-exec(compile(io.open(__file__, encoding='utf-8').read().replace('\\\\r\\\\n', '\\\\n'), __file__, 'exec'))\n+exec(compile(open(__file__, 'rb').read(), __file__, 'exec'))\n \"\"\"\n \n   class Error(Exception): pass\n", "issue": "Python 2: SyntaxError when installing distributions with coding hints in setup.py\nI think this is a regression introduced in #231, which was released in pex 1.1.7.  I cannot reproduce this problem on pex 1.1.6.  Affects Python 2 only.\n\nReproduce with:\n\n```\nwget -q 'https://github.com/pantsbuild/pex/releases/download/v1.1.7/pex27'\nchmod a+x pex27\n./pex27 backoff\n```\n\nStack trace:\n\n```\n**** Failed to install backoff-1.1.0. stdout:\n\n**** Failed to install backoff-1.1.0. stderr:\nTraceback (most recent call last):\n  File \"<stdin>\", line 7, in <module>\n  File \"setup.py\", line 0\nSyntaxError: encoding declaration in Unicode string\n\nTraceback (most recent call last):\n  File \"/private/tmp/pex/pex27/.bootstrap/_pex/pex.py\", line 328, in execute\n  File \"/private/tmp/pex/pex27/.bootstrap/_pex/pex.py\", line 260, in _wrap_coverage\n  File \"/private/tmp/pex/pex27/.bootstrap/_pex/pex.py\", line 292, in _wrap_profiling\n  File \"/private/tmp/pex/pex27/.bootstrap/_pex/pex.py\", line 371, in _execute\n  File \"/private/tmp/pex/pex27/.bootstrap/_pex/pex.py\", line 429, in execute_entry\n  File \"/private/tmp/pex/pex27/.bootstrap/_pex/pex.py\", line 447, in execute_pkg_resources\n  File \"/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/bin/pex.py\", line 538, in main\n    pex_builder = build_pex(reqs, options, resolver_options_builder)\n  File \"/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/bin/pex.py\", line 489, in build_pex\n    resolveds = resolver.resolve(resolvables)\n  File \"/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/resolver.py\", line 200, in resolve\n    dist = self.build(package, resolvable.options)\n  File \"/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/resolver.py\", line 257, in build\n    dist = super(CachingResolver, self).build(package, options)\n  File \"/Users/saj/.pex/install/pex-1.1.7-py2.py3-none-any.whl.1089a1bbad10037ad29421de8a162a77c90c8133/pex-1.1.7-py2.py3-none-any.whl/pex/resolver.py\", line 168, in build\n    raise Untranslateable('Package %s is not translateable by %s' % (package, translator))\npex.resolver.Untranslateable: Package SourcePackage(u'file:///Users/saj/.pex/build/backoff-1.1.0.tar.gz') is not translateable by ChainedTranslator(WheelTranslator, EggTranslator, SourceTranslator)\n```\n\nEven if we were to ignore the problem above, I don't think it is right to assume a UTF-8 encoding on all files.\n\nFrom [PEP 263](https://www.python.org/dev/peps/pep-0263/):\n\n> If a Unicode string with a coding declaration is passed to compile(), a SyntaxError will be raised.\n\nFrom the same PEP:\n\n> The builtin compile() API will be enhanced to accept Unicode as input. 8-bit string input is subject to the standard procedure for encoding detection as described above.\n\nInstead of opening `setup.py` as a text file and decoding it ourselves, it sounds like it should be possible to hand a binary stream directly to the `compile()` builtin and let Python deal with this muck.\n\n", "before_files": [{"content": "# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import absolute_import, print_function\n\nimport os\nimport subprocess\nimport sys\nimport tempfile\n\nfrom pkg_resources import Distribution, PathMetadata\n\nfrom .common import safe_mkdtemp, safe_rmtree\nfrom .compatibility import WINDOWS\nfrom .interpreter import PythonInterpreter\nfrom .tracer import TRACER\nfrom .version import SETUPTOOLS_REQUIREMENT, WHEEL_REQUIREMENT\n\n__all__ = (\n  'Installer',\n  'Packager'\n)\n\n\ndef after_installation(function):\n  def function_wrapper(self, *args, **kw):\n    self._installed = self.run()\n    if not self._installed:\n      raise Installer.InstallFailure('Failed to install %s' % self._source_dir)\n    return function(self, *args, **kw)\n  return function_wrapper\n\n\nclass InstallerBase(object):\n  SETUP_BOOTSTRAP_HEADER = \"import io, sys\"\n  SETUP_BOOTSTRAP_MODULE = \"sys.path.insert(0, %(path)r); import %(module)s\"\n  SETUP_BOOTSTRAP_FOOTER = \"\"\"\n__file__ = 'setup.py'\nsys.argv[0] = 'setup.py'\nexec(compile(io.open(__file__, encoding='utf-8').read().replace('\\\\r\\\\n', '\\\\n'), __file__, 'exec'))\n\"\"\"\n\n  class Error(Exception): pass\n  class InstallFailure(Error): pass\n  class IncapableInterpreter(Error): pass\n\n  def __init__(self, source_dir, strict=True, interpreter=None, install_dir=None):\n    \"\"\"\n      Create an installer from an unpacked source distribution in source_dir.\n\n      If strict=True, fail if any installation dependencies (e.g. distribute)\n      are missing.\n    \"\"\"\n    self._source_dir = source_dir\n    self._install_tmp = install_dir or safe_mkdtemp()\n    self._installed = None\n    self._strict = strict\n    self._interpreter = interpreter or PythonInterpreter.get()\n    if not self._interpreter.satisfies(self.capability) and strict:\n      raise self.IncapableInterpreter('Interpreter %s not capable of running %s' % (\n          self._interpreter.binary, self.__class__.__name__))\n\n  def mixins(self):\n    \"\"\"Return a map from import name to requirement to load into setup script prior to invocation.\n\n       May be subclassed.\n    \"\"\"\n    return {}\n\n  @property\n  def install_tmp(self):\n    return self._install_tmp\n\n  def _setup_command(self):\n    \"\"\"the setup command-line to run, to be implemented by subclasses.\"\"\"\n    raise NotImplementedError\n\n  def _postprocess(self):\n    \"\"\"a post-processing function to run following setup.py invocation.\"\"\"\n\n  @property\n  def capability(self):\n    \"\"\"returns the list of requirements for the interpreter to run this installer.\"\"\"\n    return list(self.mixins().values())\n\n  @property\n  def bootstrap_script(self):\n    bootstrap_modules = []\n    for module, requirement in self.mixins().items():\n      path = self._interpreter.get_location(requirement)\n      if not path:\n        assert not self._strict  # This should be caught by validation\n        continue\n      bootstrap_modules.append(self.SETUP_BOOTSTRAP_MODULE % {'path': path, 'module': module})\n    return '\\n'.join(\n        [self.SETUP_BOOTSTRAP_HEADER] + bootstrap_modules + [self.SETUP_BOOTSTRAP_FOOTER])\n\n  def run(self):\n    if self._installed is not None:\n      return self._installed\n\n    with TRACER.timed('Installing %s' % self._install_tmp, V=2):\n      command = [self._interpreter.binary, '-']\n      command.extend(self._setup_command())\n      po = subprocess.Popen(command,\n          stdin=subprocess.PIPE,\n          stdout=subprocess.PIPE,\n          stderr=subprocess.PIPE,\n          env=self._interpreter.sanitized_environment(),\n          cwd=self._source_dir)\n      so, se = po.communicate(self.bootstrap_script.encode('ascii'))\n      self._installed = po.returncode == 0\n\n    if not self._installed:\n      name = os.path.basename(self._source_dir)\n      print('**** Failed to install %s. stdout:\\n%s' % (name, so.decode('utf-8')), file=sys.stderr)\n      print('**** Failed to install %s. stderr:\\n%s' % (name, se.decode('utf-8')), file=sys.stderr)\n      return self._installed\n\n    self._postprocess()\n    return self._installed\n\n  def cleanup(self):\n    safe_rmtree(self._install_tmp)\n\n\nclass Installer(InstallerBase):\n  \"\"\"Install an unpacked distribution with a setup.py.\"\"\"\n\n  def __init__(self, source_dir, strict=True, interpreter=None):\n    \"\"\"\n      Create an installer from an unpacked source distribution in source_dir.\n\n      If strict=True, fail if any installation dependencies (e.g. setuptools)\n      are missing.\n    \"\"\"\n    super(Installer, self).__init__(source_dir, strict=strict, interpreter=interpreter)\n    self._egg_info = None\n    fd, self._install_record = tempfile.mkstemp()\n    os.close(fd)\n\n  def _setup_command(self):\n    return ['install',\n           '--root=%s' % self._install_tmp,\n           '--prefix=',\n           '--single-version-externally-managed',\n           '--record', self._install_record]\n\n  def _postprocess(self):\n    installed_files = []\n    egg_info = None\n    with open(self._install_record) as fp:\n      installed_files = fp.read().splitlines()\n      for line in installed_files:\n        if line.endswith('.egg-info'):\n          assert line.startswith('/'), 'Expect .egg-info to be within install_tmp!'\n          egg_info = line\n          break\n\n    if not egg_info:\n      self._installed = False\n      return self._installed\n\n    installed_files = [os.path.relpath(fn, egg_info) for fn in installed_files if fn != egg_info]\n\n    self._egg_info = os.path.join(self._install_tmp, egg_info[1:])\n    with open(os.path.join(self._egg_info, 'installed-files.txt'), 'w') as fp:\n      fp.write('\\n'.join(installed_files))\n      fp.write('\\n')\n\n    return self._installed\n\n  @after_installation\n  def egg_info(self):\n    return self._egg_info\n\n  @after_installation\n  def root(self):\n    egg_info = self.egg_info()\n    assert egg_info\n    return os.path.realpath(os.path.dirname(egg_info))\n\n  @after_installation\n  def distribution(self):\n    base_dir = self.root()\n    egg_info = self.egg_info()\n    metadata = PathMetadata(base_dir, egg_info)\n    return Distribution.from_location(base_dir, os.path.basename(egg_info), metadata=metadata)\n\n\nclass DistributionPackager(InstallerBase):\n  def mixins(self):\n    mixins = super(DistributionPackager, self).mixins().copy()\n    mixins.update(setuptools=SETUPTOOLS_REQUIREMENT)\n    return mixins\n\n  def find_distribution(self):\n    dists = os.listdir(self.install_tmp)\n    if len(dists) == 0:\n      raise self.InstallFailure('No distributions were produced!')\n    elif len(dists) > 1:\n      raise self.InstallFailure('Ambiguous source distributions found: %s' % (' '.join(dists)))\n    else:\n      return os.path.join(self.install_tmp, dists[0])\n\n\nclass Packager(DistributionPackager):\n  \"\"\"\n    Create a source distribution from an unpacked setup.py-based project.\n  \"\"\"\n\n  def _setup_command(self):\n    if WINDOWS:\n      return ['sdist', '--formats=zip', '--dist-dir=%s' % self._install_tmp]\n    else:\n      return ['sdist', '--formats=gztar', '--dist-dir=%s' % self._install_tmp]\n\n  @after_installation\n  def sdist(self):\n    return self.find_distribution()\n\n\nclass EggInstaller(DistributionPackager):\n  \"\"\"\n    Create a source distribution from an unpacked setup.py-based project.\n  \"\"\"\n\n  def _setup_command(self):\n    return ['bdist_egg', '--dist-dir=%s' % self._install_tmp]\n\n  @after_installation\n  def bdist(self):\n    return self.find_distribution()\n\n\nclass WheelInstaller(DistributionPackager):\n  \"\"\"\n    Create a source distribution from an unpacked setup.py-based project.\n  \"\"\"\n  MIXINS = {\n      'setuptools': SETUPTOOLS_REQUIREMENT,\n      'wheel': WHEEL_REQUIREMENT,\n  }\n\n  def mixins(self):\n    mixins = super(WheelInstaller, self).mixins().copy()\n    mixins.update(self.MIXINS)\n    return mixins\n\n  def _setup_command(self):\n    return ['bdist_wheel', '--dist-dir=%s' % self._install_tmp]\n\n  @after_installation\n  def bdist(self):\n    return self.find_distribution()\n", "path": "pex/installer.py"}], "after_files": [{"content": "# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).\n# Licensed under the Apache License, Version 2.0 (see LICENSE).\n\nfrom __future__ import absolute_import, print_function\n\nimport os\nimport subprocess\nimport sys\nimport tempfile\n\nfrom pkg_resources import Distribution, PathMetadata\n\nfrom .common import safe_mkdtemp, safe_rmtree\nfrom .compatibility import WINDOWS\nfrom .interpreter import PythonInterpreter\nfrom .tracer import TRACER\nfrom .version import SETUPTOOLS_REQUIREMENT, WHEEL_REQUIREMENT\n\n__all__ = (\n  'Installer',\n  'Packager'\n)\n\n\ndef after_installation(function):\n  def function_wrapper(self, *args, **kw):\n    self._installed = self.run()\n    if not self._installed:\n      raise Installer.InstallFailure('Failed to install %s' % self._source_dir)\n    return function(self, *args, **kw)\n  return function_wrapper\n\n\nclass InstallerBase(object):\n  SETUP_BOOTSTRAP_HEADER = \"import sys\"\n  SETUP_BOOTSTRAP_MODULE = \"sys.path.insert(0, %(path)r); import %(module)s\"\n  SETUP_BOOTSTRAP_FOOTER = \"\"\"\n__file__ = 'setup.py'\nsys.argv[0] = 'setup.py'\nexec(compile(open(__file__, 'rb').read(), __file__, 'exec'))\n\"\"\"\n\n  class Error(Exception): pass\n  class InstallFailure(Error): pass\n  class IncapableInterpreter(Error): pass\n\n  def __init__(self, source_dir, strict=True, interpreter=None, install_dir=None):\n    \"\"\"\n      Create an installer from an unpacked source distribution in source_dir.\n\n      If strict=True, fail if any installation dependencies (e.g. distribute)\n      are missing.\n    \"\"\"\n    self._source_dir = source_dir\n    self._install_tmp = install_dir or safe_mkdtemp()\n    self._installed = None\n    self._strict = strict\n    self._interpreter = interpreter or PythonInterpreter.get()\n    if not self._interpreter.satisfies(self.capability) and strict:\n      raise self.IncapableInterpreter('Interpreter %s not capable of running %s' % (\n          self._interpreter.binary, self.__class__.__name__))\n\n  def mixins(self):\n    \"\"\"Return a map from import name to requirement to load into setup script prior to invocation.\n\n       May be subclassed.\n    \"\"\"\n    return {}\n\n  @property\n  def install_tmp(self):\n    return self._install_tmp\n\n  def _setup_command(self):\n    \"\"\"the setup command-line to run, to be implemented by subclasses.\"\"\"\n    raise NotImplementedError\n\n  def _postprocess(self):\n    \"\"\"a post-processing function to run following setup.py invocation.\"\"\"\n\n  @property\n  def capability(self):\n    \"\"\"returns the list of requirements for the interpreter to run this installer.\"\"\"\n    return list(self.mixins().values())\n\n  @property\n  def bootstrap_script(self):\n    bootstrap_modules = []\n    for module, requirement in self.mixins().items():\n      path = self._interpreter.get_location(requirement)\n      if not path:\n        assert not self._strict  # This should be caught by validation\n        continue\n      bootstrap_modules.append(self.SETUP_BOOTSTRAP_MODULE % {'path': path, 'module': module})\n    return '\\n'.join(\n        [self.SETUP_BOOTSTRAP_HEADER] + bootstrap_modules + [self.SETUP_BOOTSTRAP_FOOTER])\n\n  def run(self):\n    if self._installed is not None:\n      return self._installed\n\n    with TRACER.timed('Installing %s' % self._install_tmp, V=2):\n      command = [self._interpreter.binary, '-']\n      command.extend(self._setup_command())\n      po = subprocess.Popen(command,\n          stdin=subprocess.PIPE,\n          stdout=subprocess.PIPE,\n          stderr=subprocess.PIPE,\n          env=self._interpreter.sanitized_environment(),\n          cwd=self._source_dir)\n      so, se = po.communicate(self.bootstrap_script.encode('ascii'))\n      self._installed = po.returncode == 0\n\n    if not self._installed:\n      name = os.path.basename(self._source_dir)\n      print('**** Failed to install %s. stdout:\\n%s' % (name, so.decode('utf-8')), file=sys.stderr)\n      print('**** Failed to install %s. stderr:\\n%s' % (name, se.decode('utf-8')), file=sys.stderr)\n      return self._installed\n\n    self._postprocess()\n    return self._installed\n\n  def cleanup(self):\n    safe_rmtree(self._install_tmp)\n\n\nclass Installer(InstallerBase):\n  \"\"\"Install an unpacked distribution with a setup.py.\"\"\"\n\n  def __init__(self, source_dir, strict=True, interpreter=None):\n    \"\"\"\n      Create an installer from an unpacked source distribution in source_dir.\n\n      If strict=True, fail if any installation dependencies (e.g. setuptools)\n      are missing.\n    \"\"\"\n    super(Installer, self).__init__(source_dir, strict=strict, interpreter=interpreter)\n    self._egg_info = None\n    fd, self._install_record = tempfile.mkstemp()\n    os.close(fd)\n\n  def _setup_command(self):\n    return ['install',\n           '--root=%s' % self._install_tmp,\n           '--prefix=',\n           '--single-version-externally-managed',\n           '--record', self._install_record]\n\n  def _postprocess(self):\n    installed_files = []\n    egg_info = None\n    with open(self._install_record) as fp:\n      installed_files = fp.read().splitlines()\n      for line in installed_files:\n        if line.endswith('.egg-info'):\n          assert line.startswith('/'), 'Expect .egg-info to be within install_tmp!'\n          egg_info = line\n          break\n\n    if not egg_info:\n      self._installed = False\n      return self._installed\n\n    installed_files = [os.path.relpath(fn, egg_info) for fn in installed_files if fn != egg_info]\n\n    self._egg_info = os.path.join(self._install_tmp, egg_info[1:])\n    with open(os.path.join(self._egg_info, 'installed-files.txt'), 'w') as fp:\n      fp.write('\\n'.join(installed_files))\n      fp.write('\\n')\n\n    return self._installed\n\n  @after_installation\n  def egg_info(self):\n    return self._egg_info\n\n  @after_installation\n  def root(self):\n    egg_info = self.egg_info()\n    assert egg_info\n    return os.path.realpath(os.path.dirname(egg_info))\n\n  @after_installation\n  def distribution(self):\n    base_dir = self.root()\n    egg_info = self.egg_info()\n    metadata = PathMetadata(base_dir, egg_info)\n    return Distribution.from_location(base_dir, os.path.basename(egg_info), metadata=metadata)\n\n\nclass DistributionPackager(InstallerBase):\n  def mixins(self):\n    mixins = super(DistributionPackager, self).mixins().copy()\n    mixins.update(setuptools=SETUPTOOLS_REQUIREMENT)\n    return mixins\n\n  def find_distribution(self):\n    dists = os.listdir(self.install_tmp)\n    if len(dists) == 0:\n      raise self.InstallFailure('No distributions were produced!')\n    elif len(dists) > 1:\n      raise self.InstallFailure('Ambiguous source distributions found: %s' % (' '.join(dists)))\n    else:\n      return os.path.join(self.install_tmp, dists[0])\n\n\nclass Packager(DistributionPackager):\n  \"\"\"\n    Create a source distribution from an unpacked setup.py-based project.\n  \"\"\"\n\n  def _setup_command(self):\n    if WINDOWS:\n      return ['sdist', '--formats=zip', '--dist-dir=%s' % self._install_tmp]\n    else:\n      return ['sdist', '--formats=gztar', '--dist-dir=%s' % self._install_tmp]\n\n  @after_installation\n  def sdist(self):\n    return self.find_distribution()\n\n\nclass EggInstaller(DistributionPackager):\n  \"\"\"\n    Create a source distribution from an unpacked setup.py-based project.\n  \"\"\"\n\n  def _setup_command(self):\n    return ['bdist_egg', '--dist-dir=%s' % self._install_tmp]\n\n  @after_installation\n  def bdist(self):\n    return self.find_distribution()\n\n\nclass WheelInstaller(DistributionPackager):\n  \"\"\"\n    Create a source distribution from an unpacked setup.py-based project.\n  \"\"\"\n  MIXINS = {\n      'setuptools': SETUPTOOLS_REQUIREMENT,\n      'wheel': WHEEL_REQUIREMENT,\n  }\n\n  def mixins(self):\n    mixins = super(WheelInstaller, self).mixins().copy()\n    mixins.update(self.MIXINS)\n    return mixins\n\n  def _setup_command(self):\n    return ['bdist_wheel', '--dist-dir=%s' % self._install_tmp]\n\n  @after_installation\n  def bdist(self):\n    return self.find_distribution()\n", "path": "pex/installer.py"}]}