Repository: pantsbuild/pants

Issue:
ASDF configuration
I was trying to use the new asdf support in 2.7 and I'm struggling a little bit.

The configuration tries to look up ASDF_DIR first, which if that doesn't exist, it tries to look for ASDF_DATA_DIR.

I've installed asdf via homebrew, which results in an ASDF_DIR of `/usr/local/opt/asdf` and ASDF_DATA_DIR is not set. I'd prefer to point at the `$HOME/.asdf` directory, but right now, this isn't possible.

I think those lookups should be switched around, maybe even naively trying the `$HOME/.asdf` directory first.

https://asdf-vm.com/manage/configuration.html#environment-variables

Current code:
https://github.com/pantsbuild/pants/blob/77b74e953bde2b7b5d8733382afffb284a34fc8a/src/python/pants/python/python_setup.py#L456-L463

Here's a slapped together version to illustrate what I mean.
```python
def get_asdf_dir(env: Environment) -> PurePath | None:
    """See https://asdf-vm.com/manage/configuration.html#environment-variables."""

    asdf_dir = Path(env.get("HOME", "")) / ".asdf"

    if not asdf_dir.exists():
        asdf_dir = env.get("ASDF_DATA_DIR", env.get("ASDF_DIR"))

    return PurePath(asdf_dir) if asdf_dir else None
```

Thanks!


Discussion:
Thinking about it a moment longer, maybe the fallback pattern would be best as:
* ASDF_DATA_DIR
* $HOME/.asdf
* ASDF_DIR

since setting the data directory is a purposeful step that should probably be respected.
pyenv is also not working for me. I noticed the code is trying PYENV_ROOT, then trying `$HOME/.cache`. Is that correct? I would expect it to try `$HOME/.pyenv`.

https://github.com/pantsbuild/pants/blob/cec93fe9280fdea212eceaad03429f23ec8c312f/src/python/pants/python/python_setup.py#L478-L486
Hey @williamscs, thanks for opening this up and pardon the issues! Re ASDF, thoughts @cjntaylor?

Re Pyenv, hm..you're right, I'm not sure why we are using `~/.cache`. @stuhood do you remember if that was intentional or a typo?
@williamscs : It looks like you've discovered at least two bugs here: sorry about that! Neither of these features have integration tests due to the difficulty of setting up those environments.

The pyenv support is fairly widely used, but apparently only among folks who `export PYENV_ROOT`!
Thanks for checking it out. I'll try to just fix the pyenv reference first. Give me a chance to set up environment for the first time. 
Created a new issue and PR for pyenv-specific bug!

Issue: https://github.com/pantsbuild/pants/issues/12526
PR: https://github.com/pantsbuild/pants/pull/12527

Resolving patch:
diff --git a/src/python/pants/python/python_setup.py b/src/python/pants/python/python_setup.py
--- a/src/python/pants/python/python_setup.py
+++ b/src/python/pants/python/python_setup.py
@@ -328,7 +328,7 @@ def get_asdf_paths(env: Environment, *, asdf_local: bool = False) -> List[str]:
         :param bool asdf_local: If True, only use the interpreter specified by
                                 '.tool-versions' file under `build_root`.
         """
-        asdf_dir = get_asdf_dir(env)
+        asdf_dir = get_asdf_data_dir(env)
         if not asdf_dir:
             return []
 
@@ -487,14 +487,29 @@ def get_pyenv_paths(env: Environment, *, pyenv_local: bool = False) -> List[str]
         return paths
 
 
-def get_asdf_dir(env: Environment) -> PurePath | None:
-    """See https://asdf-vm.com/#/core-configuration?id=environment-variables."""
-    asdf_dir = env.get("ASDF_DIR", env.get("ASDF_DATA_DIR"))
-    if not asdf_dir:
+def get_asdf_data_dir(env: Environment) -> PurePath | None:
+    """Returns the location of asdf's installed tool versions.
+
+    See https://asdf-vm.com/manage/configuration.html#environment-variables.
+
+    `ASDF_DATA_DIR` is an environment variable that can be set to override the directory
+    in which the plugins, installs, and shims are installed.
+
+    `ASDF_DIR` is another environment variable that can be set, but we ignore it since
+    that location only specifies where the asdf tool itself is installed, not the managed versions.
+
+    Per the documentation, if `ASDF_DATA_DIR` is not specified, the tool will fall back to
+    `$HOME/.asdf`, so we do that as well.
+
+    :param env: The environment to use to look up asdf.
+    :return: Path to the data directory, or None if it couldn't be found in the environment.
+    """
+    asdf_data_dir = env.get("ASDF_DATA_DIR")
+    if not asdf_data_dir:
         home = env.get("HOME")
         if home:
             return PurePath(home) / ".asdf"
-    return PurePath(asdf_dir) if asdf_dir else None
+    return PurePath(asdf_data_dir) if asdf_data_dir else None
 
 
 def get_pyenv_root(env: Environment) -> str | None:


Test patch:
diff --git a/src/python/pants/python/python_setup_test.py b/src/python/pants/python/python_setup_test.py
--- a/src/python/pants/python/python_setup_test.py
+++ b/src/python/pants/python/python_setup_test.py
@@ -10,7 +10,7 @@
 
 from pants.base.build_environment import get_pants_cachedir
 from pants.engine.environment import Environment
-from pants.python.python_setup import PythonSetup, get_asdf_dir, get_pyenv_root
+from pants.python.python_setup import PythonSetup, get_asdf_data_dir, get_pyenv_root
 from pants.testutil.rule_runner import RuleRunner
 from pants.util.contextutil import environment_as, temporary_dir
 from pants.util.dirutil import safe_mkdir_for
@@ -146,9 +146,9 @@ def test_get_asdf_dir() -> None:
     default_root = home / ".asdf"
     explicit_root = home / "explicit"
 
-    assert explicit_root == get_asdf_dir(Environment({"ASDF_DIR": f"{explicit_root}"}))
-    assert default_root == get_asdf_dir(Environment({"HOME": f"{home}"}))
-    assert get_asdf_dir(Environment({})) is None
+    assert explicit_root == get_asdf_data_dir(Environment({"ASDF_DATA_DIR": f"{explicit_root}"}))
+    assert default_root == get_asdf_data_dir(Environment({"HOME": f"{home}"}))
+    assert get_asdf_data_dir(Environment({})) is None
 
 
 def test_get_asdf_paths(rule_runner: RuleRunner) -> None:
@@ -178,13 +178,13 @@ def test_get_asdf_paths(rule_runner: RuleRunner) -> None:
         expected_asdf_local_paths,
     ):
         # Check the "all installed" fallback
-        all_paths = PythonSetup.get_asdf_paths(Environment({"ASDF_DIR": asdf_dir}))
+        all_paths = PythonSetup.get_asdf_paths(Environment({"ASDF_DATA_DIR": asdf_dir}))
 
         home_paths = PythonSetup.get_asdf_paths(
-            Environment({"HOME": home_dir, "ASDF_DIR": asdf_dir})
+            Environment({"HOME": home_dir, "ASDF_DATA_DIR": asdf_dir})
         )
         local_paths = PythonSetup.get_asdf_paths(
-            Environment({"HOME": home_dir, "ASDF_DIR": asdf_dir}), asdf_local=True
+            Environment({"HOME": home_dir, "ASDF_DATA_DIR": asdf_dir}), asdf_local=True
         )
 
         # The order the filesystem returns the "installed" folders is arbitrary
@@ -243,7 +243,7 @@ def test_expand_interpreter_search_paths(rule_runner: RuleRunner) -> None:
                     "HOME": home_dir,
                     "PATH": "/env/path1:/env/path2",
                     "PYENV_ROOT": pyenv_root,
-                    "ASDF_DIR": asdf_dir,
+                    "ASDF_DATA_DIR": asdf_dir,
                 }
             )
             expanded_paths = PythonSetup.expand_interpreter_search_paths(