Repository: pex-tool/pex

Issue:
Spurious warning when building pex with a non-default empty PEX_ROOT
Tested with 2.1.65

If `PEX_ROOT` is set and the default `~/.pex` is not writable, `pex` will warn that `PEX_ROOT` is not writable but still write to it. Once the cache has been populated, `pex` will no longer warn.

It seems deleting `$PEX_ROOT/venvs` is what triggers the warning.

```
$ chmod -w ~/.pex
$ PEX_ROOT=$(mktemp -d)
$ pex -o /tmp/out pex
/tmp/pex/lib/python3.6/site-packages/pex/pex_info.py:384: PEXWarning: PEX_ROOT is configured as /home/user/.pex but that path is un-writeable, falling back to a temporary PEX_ROOT of /tmp/tmp3pjjrz96 which will hurt performance.
  "performance.".format(pex_root=pex_root, tmp_root=tmp_root)
$ ls $PEX_ROOT
http  installed_wheels  interpreters  isolated  pip.pex  venvs
$ pex -o /tmp/out pex    # finishes with no warning
```


Discussion:
I think there is a bug here, but note that your setup has an ineffectual bash session command `PEX_ROOT=$(mktemp -d)`. SInce that `PEX_ROOT` is never exported, subsequent `pex` commands will not see it or use it. This is why you see "PEX_ROOT is configured as /home/user/.pex" and not "PEX_ROOT is configured as /tmp/...".
I actually don't repro your demo:
```
$ env | grep PEX_ROOT
$ rm -rf ~/.pex
$ mkdir ~/.pex
$ chmod -w ~/.pex
$ ls -la ~/ | grep pex
dr-xr-xr-x  2 jsirois jsirois  4096 Feb 10 19:08 .pex
drwxr-xr-x  3 jsirois jsirois  4096 Nov 24 12:34 .pex_dev
$ pex -V
/home/jsirois/.venv/pex/lib/python3.10/site-packages/pex/variables.py:574: PEXWarning: PEX_ROOT is configured as /home/jsirois/.pex but that path is un-writeable, falling back to a temporary PEX_ROOT of /tmp/tmp7ynulpds which will hurt performance.
  pex_warnings.warn(
2.1.65
$ PEX_ROOT=$(mktemp -d)
$ echo $PEX_ROOT
/tmp/tmp.I8XnpfUBxm
$ ls -l $PEX_ROOT
total 0
$ pex -o /tmp/out pex
/home/jsirois/.venv/pex/lib/python3.10/site-packages/pex/variables.py:574: PEXWarning: PEX_ROOT is configured as /home/jsirois/.pex but that path is un-writeable, falling back to a temporary PEX_ROOT of /tmp/tmp2ylh2gan which will hurt performance.
  pex_warnings.warn(
/home/jsirois/.venv/pex/lib/python3.10/site-packages/pex/pex_info.py:381: PEXWarning: PEX_ROOT is configured as /home/jsirois/.pex but that path is un-writeable, falling back to a temporary PEX_ROOT of /tmp/tmp_vm_1kjx which will hurt performance.
  pex_warnings.warn(
$ ls $PEX_ROOT
$ pex -o /tmp/out pex
/home/jsirois/.venv/pex/lib/python3.10/site-packages/pex/variables.py:574: PEXWarning: PEX_ROOT is configured as /home/jsirois/.pex but that path is un-writeable, falling back to a temporary PEX_ROOT of /tmp/tmpid9v_inz which will hurt performance.
  pex_warnings.warn(
/home/jsirois/.venv/pex/lib/python3.10/site-packages/pex/pex_info.py:381: PEXWarning: PEX_ROOT is configured as /home/jsirois/.pex but that path is un-writeable, falling back to a temporary PEX_ROOT of /tmp/tmp0mu8nanh which will hurt performance.
  pex_warnings.warn(
```
With some instrumentation and a proper export of `PEX_ROOT` I repro. It looks like the issue is in bootstrapping of a Pip PEX venv (Pex dogfoods itself to run Pip in a PEX venv when creating other PEXes):
```
$ rm -rf ~/.pex
$ mkdir ~/.pex
$ chmod -w ~/.pex
$ ls -la ~/ | grep pex
dr-xr-xr-x  2 jsirois jsirois  4096 Feb 10 19:13 .pex
drwxr-xr-x  3 jsirois jsirois  4096 Nov 24 12:34 .pex_dev
$ PEX_ROOT=$(mktemp -d)
$ echo $PEX_ROOT
/tmp/tmp.mRXnrMAjGa
$ export PEX_ROOT
$ python -mpex -o /tmp/out pex
  File "/home/jsirois/.pyenv/versions/3.10.0/lib/python3.10/threading.py", line 966, in _bootstrap
    self._bootstrap_inner()
  File "/home/jsirois/.pyenv/versions/3.10.0/lib/python3.10/threading.py", line 1009, in _bootstrap_inner
    self.run()
  File "/home/jsirois/.pyenv/versions/3.10.0/lib/python3.10/threading.py", line 946, in run
    self._target(*self._args, **self._kwargs)
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/jobs.py", line 474, in spawn_jobs
    result = Spawn(item, spawn_func(item))
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/resolver.py", line 119, in _spawn_download
    download_job = get_pip(interpreter=target.get_interpreter()).spawn_download_distributions(
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/pip.py", line 1522, in get_pip
    pip = Pip.create(path=os.path.join(ENV.PEX_ROOT, "pip.pex"), interpreter=interpreter)
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/pip.py", line 833, in create
    return cls(ensure_venv(PEX(pip_pex_path, interpreter=pip_interpreter)))
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/pex_bootstrapper.py", line 470, in ensure_venv
    shebang = populate_venv_with_pex(
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/tools/commands/venv.py", line 129, in populate_venv_with_pex
    src=PEXEnvironment.mount(pex.path()).path,
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/environment.py", line 225, in mount
    pex_root = pi.pex_root
  File "/home/jsirois/dev/pantsbuild/jsirois-pex/pex/pex_info.py", line 382, in pex_root
    traceback.print_stack()
/home/jsirois/dev/pantsbuild/jsirois-pex/pex/pex_info.py:383: PEXWarning: PEX_ROOT is configured as /home/jsirois/.pex but that path is un-writeable, falling back to a temporary PEX_ROOT of /tmp/tmpz_cjehlh which will hurt performance.
  pex_warnings.warn(
```

Resolving patch:
diff --git a/pex/environment.py b/pex/environment.py
--- a/pex/environment.py
+++ b/pex/environment.py
@@ -216,16 +216,18 @@ def mount(
         target=None,  # type: Optional[DistributionTarget]
     ):
         # type: (...) -> PEXEnvironment
-        pi = pex_info or PexInfo.from_pex(pex)
-        pex_hash = pi.pex_hash
+        if not pex_info:
+            pex_info = PexInfo.from_pex(pex)
+            pex_info.update(PexInfo.from_env())
+        pex_hash = pex_info.pex_hash
         if pex_hash is None:
             raise AssertionError(
                 "There was no pex_hash stored in {} for {}.".format(PexInfo.PATH, pex)
             )
-        pex_root = pi.pex_root
+        pex_root = pex_info.pex_root
         pex = maybe_install(pex=pex, pex_root=pex_root, pex_hash=pex_hash) or pex
         target = target or DistributionTarget.current()
-        return cls(pex=pex, pex_info=pi, target=target)
+        return cls(pex=pex, pex_info=pex_info, target=target)
 
     def __init__(
         self,


Test patch:
diff --git a/tests/integration/test_issue_1598.py b/tests/integration/test_issue_1598.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/test_issue_1598.py
@@ -0,0 +1,42 @@
+# Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
+# Licensed under the Apache License, Version 2.0 (see LICENSE).
+
+import os
+
+from pex.testing import make_env, run_pex_command
+from pex.typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from typing import Any
+
+
+def test_mount_respects_env(
+    pex_project_dir,  # type: str
+    tmpdir,  # type: Any
+):
+    # type: (...) -> None
+
+    home = os.path.join(str(tmpdir), "home")
+
+    pex_root = os.path.join(home, ".pex")
+    os.makedirs(pex_root)
+    os.chmod(pex_root, 0o555)
+    unwritable_pex_root_warning = "PEXWarning: PEX_ROOT is configured as {}".format(pex_root)
+
+    pex_file = os.path.join(str(tmpdir), "pex.pex")
+
+    result = run_pex_command(
+        args=[pex_project_dir, "-o", pex_file], env=make_env(HOME=home), quiet=True
+    )
+    result.assert_success()
+
+    assert unwritable_pex_root_warning in result.error
+
+    pex_root_override = os.path.join(str(tmpdir), "pex_root_override")
+    result = run_pex_command(
+        args=[pex_project_dir, "-o", pex_file],
+        env=make_env(HOME=home, PEX_ROOT=pex_root_override),
+        quiet=True,
+    )
+    result.assert_success()
+    assert unwritable_pex_root_warning not in result.error