Repository: pex-tool/pex

Issue:
The --cert option does not pass absolute path of cert which causes PEP-517 wheel builds with setup_requires to fail
A user with an internal index that uses a custom cert found this fails:
```
REQUESTS_CA_BUNDLE=cert.pem pip wheel --isolated 'avro-python3==1.10.0' --no-cache  --index=https://pypi.org/simple/ --extra-index-url=https://...
```

Wheras this works:
```
REQUESTS_CA_BUNDLE=$PWD/cert.pem pip wheel --isolated 'avro-python3==1.10.0' --no-cache  --index=https://pypi.org/simple/ --extra-index-url=https://
```

Right now, Pex passes --cert as-is without 1st absolutizing the path, so we get the former and fail:
https://github.com/pantsbuild/pex/blob/7ff27a66cd0a8e6ae848dd1a329732163f77da8e/pex/pip.py#L141-L151


Discussion:

Resolving patch:
diff --git a/pex/pip.py b/pex/pip.py
--- a/pex/pip.py
+++ b/pex/pip.py
@@ -134,25 +134,30 @@ def maybe_trust_insecure_host(url):
         yield "--timeout"
         yield str(network_configuration.timeout)
 
-        if network_configuration.proxy:
-            yield "--proxy"
-            yield network_configuration.proxy
-
     @staticmethod
     def _calculate_env(
         network_configuration,  # type: NetworkConfiguration
         isolated,  # type: bool
     ):
         # type: (...) -> Iterator[Tuple[str, str]]
+        if network_configuration.proxy:
+            # We use the backdoor of the universality of http(s)_proxy env var support to continue
+            # to allow Pip to operate in `--isolated` mode.
+            yield "http_proxy", network_configuration.proxy
+            yield "https_proxy", network_configuration.proxy
+
         if network_configuration.cert:
             # We use the backdoor of requests (which is vendored by Pip to handle all network
             # operations) support for REQUESTS_CA_BUNDLE when possible to continue to allow Pip to
             # operate in `--isolated` mode.
-            yield ("REQUESTS_CA_BUNDLE" if isolated else "PIP_CERT"), network_configuration.cert
+            yield (
+                ("REQUESTS_CA_BUNDLE" if isolated else "PIP_CERT"),
+                os.path.abspath(network_configuration.cert),
+            )
 
         if network_configuration.client_cert:
             assert not isolated
-            yield "PIP_CLIENT_CERT", network_configuration.client_cert
+            yield "PIP_CLIENT_CERT", os.path.abspath(network_configuration.client_cert)
 
     @classmethod
     def create(
diff --git a/pex/resolve/resolver_options.py b/pex/resolve/resolver_options.py
--- a/pex/resolve/resolver_options.py
+++ b/pex/resolve/resolver_options.py
@@ -205,7 +205,7 @@ def register_network_options(parser):
         "--proxy",
         type=str,
         default=default_network_configuration.proxy,
-        help="Specify a proxy in the form [user:passwd@]proxy.server:port.",
+        help="Specify a proxy in the form http(s)://[user:passwd@]proxy.server:port.",
     )
     parser.add_argument(
         "--cert",


Test patch:
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -4,17 +4,18 @@
 from __future__ import absolute_import
 
 import os
-import shutil
 import subprocess
+from contextlib import contextmanager
+from textwrap import dedent
 
 import pytest
 
-from pex.common import atomic_directory, safe_mkdir
-from pex.testing import make_env, run_pex_command
+from pex.common import atomic_directory, temporary_dir
+from pex.testing import PY38, ensure_python_venv, make_env, run_pex_command
 from pex.typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
-    from typing import Any
+    from typing import Any, Callable, ContextManager, Iterator, Optional, Tuple
 
 
 @pytest.fixture(scope="session")
@@ -60,3 +61,70 @@ def pex_bdist(
     wheels = os.listdir(wheels_dir)
     assert 1 == len(wheels)
     return os.path.join(wheels_dir, wheels[0])
+
+
+@pytest.fixture
+def tmp_workdir():
+    # type: () -> Iterator[str]
+    cwd = os.getcwd()
+    with temporary_dir() as tmpdir:
+        os.chdir(tmpdir)
+        try:
+            yield os.path.realpath(tmpdir)
+        finally:
+            os.chdir(cwd)
+
+
+@pytest.fixture(scope="module")
+def mitmdump():
+    # type: () -> Tuple[str, str]
+    python, pip = ensure_python_venv(PY38)
+    subprocess.check_call([pip, "install", "mitmproxy==5.3.0"])
+    mitmdump = os.path.join(os.path.dirname(python), "mitmdump")
+    return mitmdump, os.path.expanduser("~/.mitmproxy/mitmproxy-ca-cert.pem")
+
+
+@pytest.fixture
+def run_proxy(mitmdump, tmp_workdir):
+    # type: (Tuple[str, str], str) -> Callable[[Optional[str]], ContextManager[Tuple[int, str]]]
+    messages = os.path.join(tmp_workdir, "messages")
+    addon = os.path.join(tmp_workdir, "addon.py")
+    with open(addon, "w") as fp:
+        fp.write(
+            dedent(
+                """\
+                from mitmproxy import ctx
+        
+                class NotifyUp:
+                    def running(self) -> None:
+                        port = ctx.master.server.address[1]
+                        with open({msg_channel!r}, "w") as fp:
+                            print(str(port), file=fp)
+        
+                addons = [NotifyUp()]
+                """.format(
+                    msg_channel=messages
+                )
+            )
+        )
+
+    @contextmanager
+    def _run_proxy(
+        proxy_auth=None,  # type: Optional[str]
+    ):
+        # type: (...) -> Iterator[Tuple[int, str]]
+        os.mkfifo(messages)
+        proxy, ca_cert = mitmdump
+        args = [proxy, "-p", "0", "-s", addon]
+        if proxy_auth:
+            args.extend(["--proxyauth", proxy_auth])
+        proxy_process = subprocess.Popen(args)
+        try:
+            with open(messages, "r") as fp:
+                port = int(fp.readline().strip())
+                yield port, ca_cert
+        finally:
+            proxy_process.kill()
+            os.unlink(messages)
+
+    return _run_proxy
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
--- a/tests/integration/test_integration.py
+++ b/tests/integration/test_integration.py
@@ -35,7 +35,6 @@
     WheelBuilder,
     built_wheel,
     ensure_python_interpreter,
-    ensure_python_venv,
     get_dep_dist_names_from_pex,
     make_env,
     run_pex_command,
@@ -1298,18 +1297,6 @@ def test_unzip_mode():
         assert "PEXWarning: The `PEX_UNZIP` env var is deprecated." in error2.decode("utf-8")
 
 
-@pytest.fixture
-def tmp_workdir():
-    # type: () -> Iterator[str]
-    cwd = os.getcwd()
-    with temporary_dir() as tmpdir:
-        os.chdir(tmpdir)
-        try:
-            yield os.path.realpath(tmpdir)
-        finally:
-            os.chdir(cwd)
-
-
 def test_tmpdir_absolute(tmp_workdir):
     # type: (str) -> None
     result = run_pex_command(
@@ -1352,61 +1339,6 @@ def test_tmpdir_file(tmp_workdir):
     assert "is not a directory" in result.error
 
 
-@pytest.fixture(scope="module")
-def mitmdump():
-    # type: () -> Tuple[str, str]
-    python, pip = ensure_python_venv(PY38)
-    subprocess.check_call([pip, "install", "mitmproxy==5.3.0"])
-    mitmdump = os.path.join(os.path.dirname(python), "mitmdump")
-    return mitmdump, os.path.expanduser("~/.mitmproxy/mitmproxy-ca-cert.pem")
-
-
-@pytest.fixture
-def run_proxy(mitmdump, tmp_workdir):
-    # type: (Tuple[str, str], str) -> Callable[[Optional[str]], ContextManager[Tuple[int, str]]]
-    messages = os.path.join(tmp_workdir, "messages")
-    addon = os.path.join(tmp_workdir, "addon.py")
-    with open(addon, "w") as fp:
-        fp.write(
-            dedent(
-                """\
-                from mitmproxy import ctx
-        
-                class NotifyUp:
-                    def running(self) -> None:
-                        port = ctx.master.server.address[1]
-                        with open({msg_channel!r}, "w") as fp:
-                            print(str(port), file=fp)
-        
-                addons = [NotifyUp()]
-                """.format(
-                    msg_channel=messages
-                )
-            )
-        )
-
-    @contextmanager
-    def _run_proxy(
-        proxy_auth=None,  # type: Optional[str]
-    ):
-        # type: (...) -> Iterator[Tuple[int, str]]
-        os.mkfifo(messages)
-        proxy, ca_cert = mitmdump
-        args = [proxy, "-p", "0", "-s", addon]
-        if proxy_auth:
-            args.extend(["--proxyauth", proxy_auth])
-        proxy_process = subprocess.Popen(args)
-        try:
-            with open(messages, "r") as fp:
-                port = int(fp.readline().strip())
-                yield port, ca_cert
-        finally:
-            proxy_process.kill()
-            os.unlink(messages)
-
-    return _run_proxy
-
-
 EXAMPLE_PYTHON_REQUIREMENTS_URL = (
     "https://raw.githubusercontent.com/pantsbuild/example-python/"
     "c6052498f25a436f2639ccd0bc846cec1a55d7d5"
@@ -1437,7 +1369,9 @@ def req(
             EXAMPLE_PYTHON_REQUIREMENTS_URL,
             fetcher=URLFetcher(
                 NetworkConfiguration(
-                    proxy="{proxy_auth}@localhost:{port}".format(proxy_auth=proxy_auth, port=port),
+                    proxy="http://{proxy_auth}@localhost:{port}".format(
+                        proxy_auth=proxy_auth, port=port
+                    ),
                     cert=ca_cert,
                 )
             ),
diff --git a/tests/integration/test_issue_1537.py b/tests/integration/test_issue_1537.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/test_issue_1537.py
@@ -0,0 +1,40 @@
+# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
+# Licensed under the Apache License, Version 2.0 (see LICENSE).
+
+import os.path
+import shutil
+import subprocess
+
+import pytest
+
+from pex.compatibility import PY3
+from pex.testing import run_pex_command
+from pex.typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from typing import Any, Callable, ContextManager, Tuple
+
+
+@pytest.mark.skipif(
+    condition=not PY3, reason="Test relies on a distribution that is Python 3 only."
+)
+def test_rel_cert_path(
+    run_proxy,  # type: Callable[[], ContextManager[Tuple[int, str]]]
+    tmpdir,  # type: Any
+):
+    # type: (...) -> None
+    pex_file = os.path.join(str(tmpdir), "pex")
+    with run_proxy() as (port, ca_cert):
+        shutil.copy(ca_cert, "cert")
+        run_pex_command(
+            args=[
+                "--proxy",
+                "http://localhost:{port}".format(port=port),
+                "--cert",
+                "cert",
+                "avro-python3==1.10.0",
+                "-o",
+                pex_file,
+            ]
+        ).assert_success()
+        subprocess.check_call(args=[pex_file, "-c", "import avro"])