Repository: pypa/setuptools

Issue:
[BUG]  KeyError: 'unix_user' with setuptools 60.0.0
### setuptools version

setuptools-60.0.0

### Python version

Python 3.9

### OS

Ubuntu 18.04

### Additional environment information

_No response_

### Description

Getting this error with setuptools 60.0.0

Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmpfs/src/github/jax/setup.py", line 31, in <module>
        setup(
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/__init__.py", line 153, in setup
        return distutils.core.setup(**attrs)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/core.py", line 148, in setup
        return run_commands(dist)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/core.py", line 163, in run_commands
        dist.run_commands()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/dist.py", line 967, in run_commands
        self.run_command(cmd)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/dist.py", line 985, in run_command
        cmd_obj.ensure_finalized()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/cmd.py", line 107, in ensure_finalized
        self.finalize_options()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/develop.py", line 52, in finalize_options
        easy_install.finalize_options(self)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/easy_install.py", line 263, in finalize_options
        self._fix_install_dir_for_user_site()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/easy_install.py", line 375, in _fix_install_dir_for_user_site
        self.select_scheme(scheme_name)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/easy_install.py", line 716, in select_scheme
        scheme = INSTALL_SCHEMES[name]
    KeyError: 'unix_user'
    ----------------------------------------
ERROR: Command errored out with exit status 1: /usr/local/bin/python3.9 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmpfs/src/github/jax/setup.py'"'"'; __file__='"'"'/tmpfs/src/github/jax/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps --user --prefix= Check the logs for full command output.

### Expected behavior

The Error should not happen. It was working fine with setuptools 59.8.0

### How to Reproduce

1. `git clone https://github.com/google/jax.git`
2.  `cd jax`
3. `python3.9 -m pip install -e .`

Make sure the setuptools version in 60.0.0

### Output

```console
Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmpfs/src/github/jax/setup.py", line 31, in <module>
        setup(
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/__init__.py", line 153, in setup
        return distutils.core.setup(**attrs)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/core.py", line 148, in setup
        return run_commands(dist)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/core.py", line 163, in run_commands
        dist.run_commands()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/dist.py", line 967, in run_commands
        self.run_command(cmd)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/dist.py", line 985, in run_command
        cmd_obj.ensure_finalized()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/_distutils/cmd.py", line 107, in ensure_finalized
        self.finalize_options()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/develop.py", line 52, in finalize_options
        easy_install.finalize_options(self)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/easy_install.py", line 263, in finalize_options
        self._fix_install_dir_for_user_site()
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/easy_install.py", line 375, in _fix_install_dir_for_user_site
        self.select_scheme(scheme_name)
      File "/home/kbuilder/.local/lib/python3.9/site-packages/setuptools/command/easy_install.py", line 716, in select_scheme
        scheme = INSTALL_SCHEMES[name]
    KeyError: 'unix_user'
    ----------------------------------------
ERROR: Command errored out with exit status 1: /usr/local/bin/python3.9 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmpfs/src/github/jax/setup.py'"'"'; __file__='"'"'/tmpfs/src/github/jax/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps --user --prefix= Check the logs for full command output.
```


### Code of Conduct

- [X] I agree to follow the PSF Code of Conduct


Discussion:
@jaraco 
I’ll explore this soon. I suspect the issue is that there is some gap in how the install schemes are resolved on your platform. The quick fix will be to pin to Setuptools <60 or set SETUPTOOLS_USE_DISTUTILS=stdlib as a workaround in the interim.
I see that the issue is due to `easy_install` and is probably due to the fact that the `easy_install` module imports, manipulates, and consumes the INSTALL_SCHEMES from distutils, with some of the code being copy-pasted from there.
As a reminder, `easy_install` is deprecated. I'll try to find a solution for this issue, as it was unintended breakage.

_Edit_: I also recognize now that no-one has invoked `easy_install`, but instead is using the preferred `pip install -e`, which incidentally uses `easy_install`, so I retract the advice.
I've been unsuccessful triggering the issue in a virtualenv. It seems the issue is related to the implicit `--user` install created by pip, which I'm unable to trigger manually.

I was able to trigger the issue thus:

```
draft $ rm -rf jax; git clone -q gh://google/jax
draft $ $PYTHONUSERBASE=@($(pwd).strip() + '/userbase') pip-run -q setuptools==60 -- -m pip install --user --no-deps -e jax
Obtaining file:///Users/jaraco/draft/jax
  Preparing metadata (setup.py) ... done
Installing collected packages: jax
  Running setup.py develop for jax
    ERROR: Command errored out with exit status 1:
     command: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/Users/jaraco/draft/jax/setup.py'"'"'; __file__='"'"'/Users/jaraco/draft/jax/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps --user --prefix=
         cwd: /Users/jaraco/draft/jax/
    Complete output (28 lines):
    running develop
    /var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/command/easy_install.py:156: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.
      warnings.warn(
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/Users/jaraco/draft/jax/setup.py", line 31, in <module>
        setup(
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/__init__.py", line 153, in setup
        return distutils.core.setup(**attrs)
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/_distutils/core.py", line 148, in setup
        return run_commands(dist)
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/_distutils/core.py", line 163, in run_commands
        dist.run_commands()
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/_distutils/dist.py", line 967, in run_commands
        self.run_command(cmd)
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/_distutils/dist.py", line 985, in run_command
        cmd_obj.ensure_finalized()
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/_distutils/cmd.py", line 107, in ensure_finalized
        self.finalize_options()
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/command/develop.py", line 52, in finalize_options
        easy_install.finalize_options(self)
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/command/easy_install.py", line 263, in finalize_options
        self._fix_install_dir_for_user_site()
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/command/easy_install.py", line 375, in _fix_install_dir_for_user_site
        self.select_scheme(scheme_name)
      File "/var/folders/c6/v7hnmq453xb6p2dbz1gqc6rr0000gn/T/pip-run-4s2jnu9r/setuptools/command/easy_install.py", line 716, in select_scheme
        scheme = INSTALL_SCHEMES[name]
    KeyError: 'unix_user'
    ----------------------------------------
ERROR: Command errored out with exit status 1: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/Users/jaraco/draft/jax/setup.py'"'"'; __file__='"'"'/Users/jaraco/draft/jax/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' develop --no-deps --user --prefix= Check the logs for full command output.
```

Resolving patch:
diff --git a/setuptools/_distutils/command/install.py b/setuptools/_distutils/command/install.py
--- a/setuptools/_distutils/command/install.py
+++ b/setuptools/_distutils/command/install.py
@@ -123,6 +123,47 @@ def _get_implementation():
         return 'Python'
 
 
+def _select_scheme(ob, name):
+    vars(ob).update(_remove_set(ob, _scheme_attrs(_resolve_scheme(name))))
+
+
+def _remove_set(ob, attrs):
+    """
+    Include only attrs that are None in ob.
+    """
+    return {
+        key: value
+        for key, value in attrs.items()
+        if getattr(ob, key) is None
+    }
+
+
+def _resolve_scheme(name):
+    os_name, sep, key = name.partition('_')
+    try:
+        resolved = sysconfig.get_preferred_scheme(key)
+    except Exception:
+        resolved = _pypy_hack(name)
+    return resolved
+
+
+def _scheme_attrs(name):
+    """Resolve install directories by applying the install schemes."""
+    scheme = _load_schemes()[name]
+    return {
+        f'install_{key}': scheme[key]
+        for key in SCHEME_KEYS
+    }
+
+
+def _pypy_hack(name):
+    PY37 = sys.version_info < (3, 8)
+    old_pypy = hasattr(sys, 'pypy_version_info') and PY37
+    prefix = not name.endswith(('_user', '_home'))
+    pypy_name = 'pypy' + '_nt' * (os.name == 'nt')
+    return pypy_name if old_pypy and prefix else name
+
+
 class install(Command):
 
     description = "install everything from build directory"
@@ -520,29 +561,7 @@ def finalize_other(self):
                       "I don't know how to install stuff on '%s'" % os.name)
 
     def select_scheme(self, name):
-        os_name, sep, key = name.partition('_')
-        try:
-            resolved = sysconfig.get_preferred_scheme(key)
-        except Exception:
-            resolved = self._pypy_hack(name)
-        return self._select_scheme(resolved)
-
-    def _select_scheme(self, name):
-        """Sets the install directories by applying the install schemes."""
-        # it's the caller's problem if they supply a bad name!
-        scheme = _load_schemes()[name]
-        for key in SCHEME_KEYS:
-            attrname = 'install_' + key
-            if getattr(self, attrname) is None:
-                setattr(self, attrname, scheme[key])
-
-    @staticmethod
-    def _pypy_hack(name):
-        PY37 = sys.version_info < (3, 8)
-        old_pypy = hasattr(sys, 'pypy_version_info') and PY37
-        prefix = not name.endswith(('_user', '_home'))
-        pypy_name = 'pypy' + '_nt' * (os.name == 'nt')
-        return pypy_name if old_pypy and prefix else name
+        _select_scheme(self, name)
 
     def _expand_attrs(self, attrs):
         for attr in attrs:
diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py
--- a/setuptools/command/easy_install.py
+++ b/setuptools/command/easy_install.py
@@ -17,10 +17,10 @@
     DistutilsArgError, DistutilsOptionError,
     DistutilsError, DistutilsPlatformError,
 )
-from distutils.command.install import INSTALL_SCHEMES, SCHEME_KEYS
 from distutils import log, dir_util
 from distutils.command.build_scripts import first_line_re
 from distutils.spawn import find_executable
+from distutils.command import install
 import sys
 import os
 import zipimport
@@ -251,7 +251,14 @@ def finalize_options(self):  # noqa: C901  # is too complex (25)  # FIXME
             'exec_prefix': exec_prefix,
             # Only python 3.2+ has abiflags
             'abiflags': getattr(sys, 'abiflags', ''),
+            'platlibdir': getattr(sys, 'platlibdir', 'lib'),
         }
+        with contextlib.suppress(AttributeError):
+            # only for distutils outside stdlib
+            self.config_vars.update({
+                'implementation_lower': install._get_implementation().lower(),
+                'implementation': install._get_implementation(),
+            })
 
         if site.ENABLE_USER_SITE:
             self.config_vars['userbase'] = self.install_userbase
@@ -711,13 +718,11 @@ def install_item(self, spec, download, tmpdir, deps, install_needed=False):
                     return dist
 
     def select_scheme(self, name):
-        """Sets the install directories by applying the install schemes."""
-        # it's the caller's problem if they supply a bad name!
-        scheme = INSTALL_SCHEMES[name]
-        for key in SCHEME_KEYS:
-            attrname = 'install_' + key
-            if getattr(self, attrname) is None:
-                setattr(self, attrname, scheme[key])
+        try:
+            install._select_scheme(self, name)
+        except AttributeError:
+            # stdlib distutils
+            install.install.select_scheme(self, name)
 
     # FIXME: 'easy_install.process_distribution' is too complex (12)
     def process_distribution(  # noqa: C901


Test patch: