From: Anthony Sottile <asottile@umich.edu>
Date: Mon, 12 Sep 2022 11:11:20 -0400
Subject: fix building python3.10 virtualenvs on debian derivatives (#2415)

Origin: https://github.com/pypa/virtualenv/pull/2415
---
 docs/changelog/2350.bugfix.rst               |  1 +
 src/virtualenv/discovery/py_info.py          | 17 ++++++-
 tests/unit/discovery/py_info/test_py_info.py | 71 ++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 2 deletions(-)
 create mode 100644 docs/changelog/2350.bugfix.rst

diff --git a/docs/changelog/2350.bugfix.rst b/docs/changelog/2350.bugfix.rst
new file mode 100644
index 0000000..87a66a2
--- /dev/null
+++ b/docs/changelog/2350.bugfix.rst
@@ -0,0 +1 @@
+Fix selected scheme on debian derivatives for python 3.10 when ``python3-distutils`` is not installed or the ``venv`` scheme is not avaiable - by :user:`asottile`.
diff --git a/src/virtualenv/discovery/py_info.py b/src/virtualenv/discovery/py_info.py
index 2a648e0..7c680ee 100644
--- a/src/virtualenv/discovery/py_info.py
+++ b/src/virtualenv/discovery/py_info.py
@@ -77,10 +77,23 @@ class PythonInfo(object):
         self.file_system_encoding = u(sys.getfilesystemencoding())
         self.stdout_encoding = u(getattr(sys.stdout, "encoding", None))
 
-        if "venv" in sysconfig.get_scheme_names():
+        scheme_names = sysconfig.get_scheme_names()
+
+        if "venv" in scheme_names:
             self.sysconfig_scheme = "venv"
             self.sysconfig_paths = {
-                u(i): u(sysconfig.get_path(i, expand=False, scheme="venv")) for i in sysconfig.get_path_names()
+                u(i): u(sysconfig.get_path(i, expand=False, scheme=self.sysconfig_scheme))
+                for i in sysconfig.get_path_names()
+            }
+            # we cannot use distutils at all if "venv" exists, distutils don't know it
+            self.distutils_install = {}
+        # debian / ubuntu python 3.10 without `python3-distutils` will report
+        # mangled `local/bin` / etc. names for the default prefix
+        # intentionally select `posix_prefix` which is the unaltered posix-like paths
+        elif sys.version_info[:2] == (3, 10) and "deb_system" in scheme_names:
+            self.sysconfig_scheme = "posix_prefix"
+            self.sysconfig_paths = {
+                i: sysconfig.get_path(i, expand=False, scheme=self.sysconfig_scheme) for i in sysconfig.get_path_names()
             }
             # we cannot use distutils at all if "venv" exists, distutils don't know it
             self.distutils_install = {}
diff --git a/tests/unit/discovery/py_info/test_py_info.py b/tests/unit/discovery/py_info/test_py_info.py
index 9d3d762..f621224 100644
--- a/tests/unit/discovery/py_info/test_py_info.py
+++ b/tests/unit/discovery/py_info/test_py_info.py
@@ -1,4 +1,5 @@
 import copy
+import functools
 import itertools
 import json
 import logging
@@ -375,3 +376,73 @@ def test_custom_venv_install_scheme_is_prefered(mocker):
     pyver = f"{pyinfo.version_info.major}.{pyinfo.version_info.minor}"
     assert pyinfo.install_path("scripts") == "bin"
     assert pyinfo.install_path("purelib").replace(os.sep, "/") == f"lib/python{pyver}/site-packages"
+
+
+@pytest.mark.skipif(sys.version_info[:2] != (3, 10), reason="3.10 specific")
+def test_uses_posix_prefix_on_debian_3_10_without_venv(mocker):
+    # this is taken from ubuntu 22.04 /usr/lib/python3.10/sysconfig.py
+    sysconfig_install_schemes = {
+        "posix_prefix": {
+            "stdlib": "{installed_base}/{platlibdir}/python{py_version_short}",
+            "platstdlib": "{platbase}/{platlibdir}/python{py_version_short}",
+            "purelib": "{base}/lib/python{py_version_short}/site-packages",
+            "platlib": "{platbase}/{platlibdir}/python{py_version_short}/site-packages",
+            "include": "{installed_base}/include/python{py_version_short}{abiflags}",
+            "platinclude": "{installed_platbase}/include/python{py_version_short}{abiflags}",
+            "scripts": "{base}/bin",
+            "data": "{base}",
+        },
+        "posix_home": {
+            "stdlib": "{installed_base}/lib/python",
+            "platstdlib": "{base}/lib/python",
+            "purelib": "{base}/lib/python",
+            "platlib": "{base}/lib/python",
+            "include": "{installed_base}/include/python",
+            "platinclude": "{installed_base}/include/python",
+            "scripts": "{base}/bin",
+            "data": "{base}",
+        },
+        "nt": {
+            "stdlib": "{installed_base}/Lib",
+            "platstdlib": "{base}/Lib",
+            "purelib": "{base}/Lib/site-packages",
+            "platlib": "{base}/Lib/site-packages",
+            "include": "{installed_base}/Include",
+            "platinclude": "{installed_base}/Include",
+            "scripts": "{base}/Scripts",
+            "data": "{base}",
+        },
+        "deb_system": {
+            "stdlib": "{installed_base}/{platlibdir}/python{py_version_short}",
+            "platstdlib": "{platbase}/{platlibdir}/python{py_version_short}",
+            "purelib": "{base}/lib/python3/dist-packages",
+            "platlib": "{platbase}/{platlibdir}/python3/dist-packages",
+            "include": "{installed_base}/include/python{py_version_short}{abiflags}",
+            "platinclude": "{installed_platbase}/include/python{py_version_short}{abiflags}",
+            "scripts": "{base}/bin",
+            "data": "{base}",
+        },
+        "posix_local": {
+            "stdlib": "{installed_base}/{platlibdir}/python{py_version_short}",
+            "platstdlib": "{platbase}/{platlibdir}/python{py_version_short}",
+            "purelib": "{base}/local/lib/python{py_version_short}/dist-packages",
+            "platlib": "{platbase}/local/lib/python{py_version_short}/dist-packages",
+            "include": "{installed_base}/local/include/python{py_version_short}{abiflags}",
+            "platinclude": "{installed_platbase}/local/include/python{py_version_short}{abiflags}",
+            "scripts": "{base}/local/bin",
+            "data": "{base}",
+        },
+    }
+    # reset the default in case we're on a system which doesn't have this problem
+    sysconfig_get_path = functools.partial(sysconfig.get_path, scheme="posix_local")
+
+    # make it look like python3-distutils is not available
+    mocker.patch.dict(sys.modules, {"distutils.command": None})
+    mocker.patch("sysconfig._INSTALL_SCHEMES", sysconfig_install_schemes)
+    mocker.patch("sysconfig.get_path", sysconfig_get_path)
+    mocker.patch("sysconfig.get_default_scheme", return_value="posix_local")
+
+    pyinfo = PythonInfo()
+    pyver = f"{pyinfo.version_info.major}.{pyinfo.version_info.minor}"
+    assert pyinfo.install_path("scripts") == "bin"
+    assert pyinfo.install_path("purelib").replace(os.sep, "/") == f"lib/python{pyver}/site-packages"
