Repository: pex-tool/pex

Issue:
Master CI is broken for OSX python3.8 `tests/test_bdist_pex.py`
In short the tests in `tests/test_bdist_pex.py` try to run `python setup.py bdist_pex ...` but either the Pex codebase is not on the sys.path / PYTHONPATH or else it is but the distutils `bdist_pex` `Command` provded by Pex is not being registered / discovered.

Errors look like:
```
tests/test_bdist_pex.py::test_entry_points_dict usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help
error: invalid command 'bdist_pex'
```


Discussion:

Resolving patch:
diff --git a/pex/interpreter.py b/pex/interpreter.py
--- a/pex/interpreter.py
+++ b/pex/interpreter.py
@@ -549,7 +549,14 @@ def __repr__(self):
     )
 
 
-def spawn_python_job(args, env=None, interpreter=None, expose=None, **subprocess_kwargs):
+def spawn_python_job(
+  args,
+  env=None,
+  interpreter=None,
+  expose=None,
+  pythonpath=None,
+  **subprocess_kwargs
+):
   """Spawns a python job.
 
   :param args: The arguments to pass to the python interpreter.
@@ -561,21 +568,25 @@ def spawn_python_job(args, env=None, interpreter=None, expose=None, **subprocess
                       interpreter.
   :type interpreter: :class:`PythonInterpreter`
   :param expose: The names of any vendored distributions to expose to the spawned python process.
+                 These will be appended to `pythonpath` if passed.
   :type expose: list of str
+  :param pythonpath: The PYTHONPATH to expose to the spawned python process. These will be
+                     pre-pended to the `expose` path if passed.
+  :type pythonpath: list of str
   :param subprocess_kwargs: Any additional :class:`subprocess.Popen` kwargs to pass through.
   :returns: A job handle to the spawned python process.
   :rtype: :class:`Job`
   """
+  pythonpath = list(pythonpath or ())
   if expose:
     subprocess_env = (env or os.environ).copy()
     # In order to expose vendored distributions with their un-vendored import paths in-tact, we
     # need to set `__PEX_UNVENDORED__`. See: vendor.__main__.ImportRewriter._modify_import.
     subprocess_env['__PEX_UNVENDORED__'] = '1'
 
-    pythonpath = third_party.expose(expose)
+    pythonpath.extend(third_party.expose(expose))
   else:
     subprocess_env = env
-    pythonpath = None
 
   interpreter = interpreter or PythonInterpreter.get()
   cmd, process = interpreter.open_process(


Test patch:
diff --git a/tests/test_bdist_pex.py b/tests/test_bdist_pex.py
--- a/tests/test_bdist_pex.py
+++ b/tests/test_bdist_pex.py
@@ -11,14 +11,22 @@
 from pex.testing import WheelBuilder, make_project, temporary_content
 
 
+def pex_project_dir():
+  return subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).decode('utf-8').strip()
+
+
 @contextmanager
 def bdist_pex(project_dir, bdist_args=None):
   with temporary_dir() as dist_dir:
-    cmd = ['setup.py', 'bdist_pex', '--bdist-dir={}'.format(dist_dir)]
+    cmd = [
+      'setup.py',
+      '--command-packages', 'pex.commands',
+      'bdist_pex', '--bdist-dir={}'.format(dist_dir)
+    ]
     if bdist_args:
       cmd.extend(bdist_args)
 
-    spawn_python_job(args=cmd, cwd=project_dir).wait()
+    spawn_python_job(args=cmd, cwd=project_dir, pythonpath=[pex_project_dir()]).wait()
     dists = os.listdir(dist_dir)
     assert len(dists) == 1
     yield os.path.join(dist_dir, dists[0])