Description: Debian: Add a distutils option --install-layout=deb
 This option:
  - installs into $prefix/dist-packages instead of $prefix/site-packages.
  - doesn't encode the python version into the egg name.
 .
 We install modules into dist-packages so that a local admin can build their
 own cpython from source, and they won't see each others' installed modules.
 This keeps Debian packaged applications working correctly, isolated from the
 local cpython.
 .
 Customize site.py to import from Debian's dist-packages layout.

Forwarded: not-needed

--- a/Lib/distutils/command/install_egg_info.py
+++ b/Lib/distutils/command/install_egg_info.py
@@ -14,18 +14,38 @@
     description = "Install package's PKG-INFO metadata as an .egg-info file"
     user_options = [
         ('install-dir=', 'd', "directory to install to"),
+        ('install-layout', None, "custom installation layout"),
     ]
 
     def initialize_options(self):
         self.install_dir = None
+        self.install_layout = None
+        self.prefix_option = None
 
     def finalize_options(self):
         self.set_undefined_options('install_lib',('install_dir','install_dir'))
-        basename = "%s-%s-py%d.%d.egg-info" % (
-            to_filename(safe_name(self.distribution.get_name())),
-            to_filename(safe_version(self.distribution.get_version())),
-            *sys.version_info[:2]
-        )
+        self.set_undefined_options('install',('install_layout','install_layout'))
+        self.set_undefined_options('install',('prefix_option','prefix_option'))
+        if self.install_layout:
+            if not self.install_layout.lower() in ['deb', 'unix']:
+                raise DistutilsOptionError(
+                    "unknown value for --install-layout")
+            no_pyver = (self.install_layout.lower() == 'deb')
+        elif self.prefix_option:
+            no_pyver = False
+        else:
+            no_pyver = True
+        if no_pyver:
+            basename = "%s-%s.egg-info" % (
+                to_filename(safe_name(self.distribution.get_name())),
+                to_filename(safe_version(self.distribution.get_version()))
+                )
+        else:
+            basename = "%s-%s-py%d.%d.egg-info" % (
+                to_filename(safe_name(self.distribution.get_name())),
+                to_filename(safe_version(self.distribution.get_version())),
+                *sys.version_info[:2]
+            )
         self.target = os.path.join(self.install_dir, basename)
         self.outputs = [self.target]
 
--- a/Lib/distutils/command/install.py
+++ b/Lib/distutils/command/install.py
@@ -10,7 +10,7 @@
 from distutils import log
 from distutils.core import Command
 from distutils.debug import DEBUG
-from distutils.sysconfig import get_config_vars
+from distutils.sysconfig import get_config_vars, is_virtual_environment
 from distutils.errors import DistutilsPlatformError
 from distutils.file_util import write_file
 from distutils.util import convert_path, subst_vars, change_root
@@ -35,6 +35,30 @@
 # of this information should switch to using sysconfig directly.
 INSTALL_SCHEMES = {"unix_prefix": {}, "unix_home": {}, "nt": {}}
 
+# add the Debian install schemes here until they are added to sysconfig
+INSTALL_SCHEMES['unix_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/{platlibdir}/python{py_version_short}/dist-packages',
+    'include': '{installed_base}/include/python{py_version_short}{abiflags}',
+    'headers': '{base}/local/include/python{py_version_short}{abiflags}',
+    'platinclude': '{installed_platbase}/include/python{py_version_short}{abiflags}',
+    'scripts': '{base}/local/bin',
+    'data': '{base}/local',
+}
+INSTALL_SCHEMES['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}',
+    'headers': '{installed_base}/include/python{py_version_short}{abiflags}',
+    'platinclude': '{installed_platbase}/include/python{py_version_short}{abiflags}',
+    'scripts': '{base}/bin',
+    'data': '{base}',
+}
+
 # Copy from sysconfig._INSTALL_SCHEMES
 for key in SCHEME_KEYS:
     for distutils_scheme_name, sys_scheme_name in (
@@ -148,6 +172,9 @@
 
         ('record=', None,
          "filename in which to record list of installed files"),
+
+        ('install-layout=', None,
+         "installation layout to choose (known values: deb, unix)"),
         ]
 
     boolean_options = ['compile', 'force', 'skip-build']
@@ -168,6 +195,7 @@
         self.exec_prefix = None
         self.home = None
         self.user = 0
+        self.prefix_option = None
 
         # These select only the installation base; it's up to the user to
         # specify the installation scheme (currently, that means supplying
@@ -190,6 +218,9 @@
             self.install_userbase = USER_BASE
             self.install_usersite = USER_SITE
 
+        # enable custom installation, known values: deb
+        self.install_layout = None
+
         self.compile = None
         self.optimize = None
 
@@ -436,6 +467,7 @@
             self.install_base = self.install_platbase = self.home
             self.select_scheme("unix_home")
         else:
+            self.prefix_option = self.prefix
             if self.prefix is None:
                 if self.exec_prefix is not None:
                     raise DistutilsOptionError(
@@ -450,7 +482,23 @@
 
             self.install_base = self.prefix
             self.install_platbase = self.exec_prefix
-            self.select_scheme("unix_prefix")
+            if self.install_layout:
+                if self.install_layout.lower() in ['deb']:
+                    self.select_scheme("deb_system")
+                elif self.install_layout.lower() in ['unix']:
+                    self.select_scheme("unix_prefix")
+                else:
+                    raise DistutilsOptionError(
+                        "unknown value for --install-layout")
+            elif ((self.prefix_option and
+                   os.path.normpath(self.prefix) != '/usr/local')
+                  or is_virtual_environment()):
+                self.select_scheme("unix_prefix")
+            else:
+                if os.path.normpath(self.prefix) == '/usr/local':
+                    self.prefix = self.exec_prefix = '/usr'
+                    self.install_base = self.install_platbase = '/usr'
+                self.select_scheme("unix_local")
 
     def finalize_other(self):
         """Finalizes options for non-posix platforms"""
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -266,6 +266,10 @@
         compiler.shared_lib_extension = shlib_suffix
 
 
+def is_virtual_environment():
+    return sys.base_prefix != sys.prefix or hasattr(sys, "real_prefix")
+
+
 def get_python_inc(plat_specific=0, prefix=None):
     """Return the directory containing installed Python header files.
 
@@ -320,6 +324,7 @@
     If 'prefix' is supplied, use it instead of sys.base_prefix or
     sys.base_exec_prefix -- i.e., ignore 'plat_specific'.
     """
+    is_default_prefix = not prefix or os.path.normpath(prefix) in ('/usr', '/usr/local')
     if prefix is None:
         if standard_lib:
             prefix = plat_specific and BASE_EXEC_PREFIX or BASE_PREFIX
@@ -338,6 +343,8 @@
                                  "python" + get_python_version())
         if standard_lib:
             return libpython
+        elif is_default_prefix and not is_virtual_environment():
+            return os.path.join(prefix, "lib", "python3", "dist-packages")
         else:
             return os.path.join(libpython, "site-packages")
     elif os.name == "nt":
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -7,12 +7,18 @@
 This will append site-specific paths to the module search path.  On
 Unix (including Mac OSX), it starts with sys.prefix and
 sys.exec_prefix (if different) and appends
-lib/python<version>/site-packages.
+lib/python3/dist-packages.
 On other platforms (such as Windows), it tries each of the
 prefixes directly, as well as with lib/site-packages appended.  The
 resulting directories, if they exist, are appended to sys.path, and
 also inspected for path configuration files.
 
+For Debian and derivatives, this sys.path is augmented with directories
+for packages distributed within the distribution. Local addons go
+into /usr/local/lib/python<version>/dist-packages, Debian addons
+install into /usr/lib/python3/dist-packages.
+/usr/lib/python<version>/site-packages is not used.
+
 If a file named "pyvenv.cfg" exists one directory above sys.executable,
 sys.prefix and sys.exec_prefix are set to that directory and
 it is also checked for site-packages (sys.base_prefix and
@@ -356,6 +362,8 @@
     if prefixes is None:
         prefixes = PREFIXES
 
+    is_virtual_environment = sys.base_prefix != sys.prefix or hasattr(sys, "real_prefix")
+
     for prefix in prefixes:
         if not prefix or prefix in seen:
             continue
@@ -366,10 +374,21 @@
             libdirs.append("lib")
 
         if os.sep == '/':
+            if is_virtual_environment:
+                sitepackages.append(os.path.join(prefix, "lib",
+                                                 "python%d.%d" % sys.version_info[:2],
+                                                 "site-packages"))
+            sitepackages.append(os.path.join(prefix, "local/lib",
+                                             "python%d.%d" % sys.version_info[:2],
+                                             "dist-packages"))
+            sitepackages.append(os.path.join(prefix, "lib",
+                                             "python3",
+                                             "dist-packages"))
+            # this one is deprecated for Debian
             for libdir in libdirs:
                 path = os.path.join(prefix, libdir,
                                     "python%d.%d" % sys.version_info[:2],
-                                    "site-packages")
+                                    "dist-packages")
                 sitepackages.append(path)
         else:
             sitepackages.append(prefix)
--- a/Lib/test/test_site.py
+++ b/Lib/test/test_site.py
@@ -284,16 +284,16 @@
         if os.sep == '/':
             # OS X, Linux, FreeBSD, etc
             if sys.platlibdir != "lib":
-                self.assertEqual(len(dirs), 2)
-                wanted = os.path.join('xoxo', sys.platlibdir,
+                self.assertEqual(len(dirs), 3)
+                wanted = os.path.join('xoxo', 'local', 'lib',
                                       'python%d.%d' % sys.version_info[:2],
-                                      'site-packages')
+                                      'dist-packages')
                 self.assertEqual(dirs[0], wanted)
             else:
-                self.assertEqual(len(dirs), 1)
+                self.assertEqual(len(dirs), 3)
             wanted = os.path.join('xoxo', 'lib',
                                   'python%d.%d' % sys.version_info[:2],
-                                  'site-packages')
+                                  'dist-packages')
             self.assertEqual(dirs[-1], wanted)
         else:
             # other platforms
--- a/Lib/distutils/tests/test_bdist_dumb.py
+++ b/Lib/distutils/tests/test_bdist_dumb.py
@@ -85,7 +85,7 @@
             fp.close()
 
         contents = sorted(filter(None, map(os.path.basename, contents)))
-        wanted = ['foo-0.1-py%s.%s.egg-info' % sys.version_info[:2], 'foo.py']
+        wanted = ['foo-0.1.egg-info', 'foo.py']
         if not sys.dont_write_bytecode:
             wanted.append('foo.%s.pyc' % sys.implementation.cache_tag)
         self.assertEqual(contents, sorted(wanted))
--- a/Lib/distutils/tests/test_install.py
+++ b/Lib/distutils/tests/test_install.py
@@ -205,7 +205,7 @@
         found = [os.path.basename(line) for line in content.splitlines()]
         expected = ['hello.py', 'hello.%s.pyc' % sys.implementation.cache_tag,
                     'sayhi',
-                    'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2]]
+                    'UNKNOWN-0.0.0.egg-info']
         self.assertEqual(found, expected)
 
     def test_record_extensions(self):
@@ -238,7 +238,7 @@
 
         found = [os.path.basename(line) for line in content.splitlines()]
         expected = [_make_ext_name('xx'),
-                    'UNKNOWN-0.0.0-py%s.%s.egg-info' % sys.version_info[:2]]
+                    'UNKNOWN-0.0.0.egg-info']
         self.assertEqual(found, expected)
 
     def test_debug_mode(self):
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -509,6 +509,7 @@
                                  'marshal', 'posix', 'signal', 'sys',
                                  '_thread', 'zipimport') or
              (file.startswith(basedir) and
+              not file.startswith(os.path.join(basedir, 'dist-packages')) and
               not file.startswith(os.path.join(basedir, 'site-packages')))) and
             object.__name__ not in ('xml.etree', 'test.pydoc_mod')):
             if docloc.startswith(("http://", "https://")):
--- /dev/null
+++ b/Lib/_distutils_system_mod.py
@@ -0,0 +1,180 @@
+"""
+Apply Debian-specific patches to distutils commands.
+
+Extracts the customized behavior from patches as reported
+in pypa/distutils#2 and applies those customizations (except
+for scheme definitions) to those commands.
+
+Place this module somewhere in sys.path to take effect.
+"""
+
+import os
+import sys
+import sysconfig
+
+import distutils.sysconfig
+import distutils.command.install as orig_install
+import distutils.command.install_egg_info as orig_install_egg_info
+from distutils.command.install_egg_info import (
+    to_filename,
+    safe_name,
+    safe_version,
+    )
+from distutils.errors import DistutilsOptionError
+
+
+class install(orig_install.install):
+    user_options = list(orig_install.install.user_options) + [
+        ('install-layout=', None,
+         "installation layout to choose (known values: deb, unix)"),
+    ]
+
+    def initialize_options(self):
+        super().initialize_options()
+        self.prefix_option = None
+        self.install_layout = None
+
+    def select_scheme(self, name):
+        if name == "posix_prefix":
+            if self.install_layout:
+                if self.install_layout.lower() in ['deb']:
+                    name = "deb_system"
+                elif self.install_layout.lower() in ['unix']:
+                    name = "posix_prefix"
+                else:
+                    raise DistutilsOptionError(
+                        "unknown value for --install-layout")
+            elif ((self.prefix_option and
+                   os.path.normpath(self.prefix) != '/usr/local')
+                  or is_virtual_environment()):
+                name = "posix_prefix"
+            else:
+                if os.path.normpath(self.prefix) == '/usr/local':
+                    self.prefix = self.exec_prefix = '/usr'
+                    self.install_base = self.install_platbase = '/usr'
+                name = "posix_local"
+        super().select_scheme(name)
+
+    def finalize_unix(self):
+        self.prefix_option = self.prefix
+        super().finalize_unix()
+
+
+class install_egg_info(orig_install_egg_info.install_egg_info):
+    user_options = list(orig_install_egg_info.install_egg_info.user_options) + [
+        ('install-layout', None, "custom installation layout"),
+    ]
+
+    def initialize_options(self):
+        super().initialize_options()
+        self.prefix_option = None
+        self.install_layout = None
+
+    def finalize_options(self):
+        self.set_undefined_options('install',('install_layout','install_layout'))
+        self.set_undefined_options('install',('prefix_option','prefix_option'))
+        super().finalize_options()
+
+    @property
+    def basename(self):
+        if self.install_layout:
+            if not self.install_layout.lower() in ['deb', 'unix']:
+                raise DistutilsOptionError(
+                    "unknown value for --install-layout")
+            no_pyver = (self.install_layout.lower() == 'deb')
+        elif self.prefix_option:
+            no_pyver = False
+        else:
+            no_pyver = True
+        if no_pyver:
+            basename = "%s-%s.egg-info" % (
+                to_filename(safe_name(self.distribution.get_name())),
+                to_filename(safe_version(self.distribution.get_version()))
+                )
+        else:
+            basename = "%s-%s-py%d.%d.egg-info" % (
+                to_filename(safe_name(self.distribution.get_name())),
+                to_filename(safe_version(self.distribution.get_version())),
+                *sys.version_info[:2]
+            )
+        return basename
+
+
+def is_virtual_environment():
+    return sys.base_prefix != sys.prefix or hasattr(sys, "real_prefix")
+
+
+def _posix_lib(standard_lib, libpython, early_prefix, prefix):
+    is_default_prefix = not early_prefix or os.path.normpath(early_prefix) in ('/usr', '/usr/local')
+    if standard_lib:
+        return libpython
+    elif is_default_prefix and not is_virtual_environment():
+        return os.path.join(prefix, "lib", "python3", "dist-packages")
+    else:
+        return os.path.join(libpython, "site-packages")
+
+
+def _inject_headers(name, scheme):
+    """
+    Given a scheme name and the resolved scheme,
+    if the scheme does not include headers, resolve
+    the fallback scheme for the name and use headers
+    from it. pypa/distutils#88
+
+    headers: module headers install location (posix_local is /local/ prefixed)
+    include: cpython headers (Python.h)
+    See also: bpo-44445
+    """
+    if 'headers' not in scheme:
+        if name == 'posix_prefix':
+            headers = scheme['include']
+        else:
+            headers = orig_install.INSTALL_SCHEMES['posix_prefix']['headers']
+        if name == 'posix_local' and '/local/' not in headers:
+            headers = headers.replace('/include/', '/local/include/')
+        scheme['headers'] = headers
+    return scheme
+
+
+def load_schemes_wrapper(_load_schemes):
+    """
+    Implement the _inject_headers modification, above, but before
+    _inject_headers() was introduced, upstream. So, slower and messier.
+    """
+    def wrapped_load_schemes():
+        schemes = _load_schemes()
+        for name, scheme in schemes.items():
+            _inject_headers(name, scheme)
+        return schemes
+    return wrapped_load_schemes
+
+
+def add_debian_schemes(schemes):
+    """
+    Ensure that the custom schemes we refer to above are present in schemes.
+    """
+    for name in ('posix_prefix', 'posix_local', 'deb_system'):
+        if name not in schemes:
+            scheme = sysconfig.get_paths(name, expand=False)
+            schemes[name] = _inject_headers(name, scheme)
+
+
+def apply_customizations():
+    orig_install.install = install
+    orig_install_egg_info.install_egg_info = install_egg_info
+    distutils.sysconfig._posix_lib = _posix_lib
+
+    if hasattr(orig_install, '_inject_headers'):
+        # setuptools-bundled distutils >= 60.0.5
+        orig_install._inject_headers = _inject_headers
+    elif hasattr(orig_install, '_load_schemes'):
+        # setuptools-bundled distutils >= 59.2.0
+        orig_install._load_schemes = load_schemes_wrapper(orig_install._load_schemes)
+    else:
+        # older version with only statically defined schemes
+        # this includes the version bundled with Python 3.10 that has our
+        # schemes already included
+        add_debian_schemes(orig_install.INSTALL_SCHEMES)
+
+
+apply_customizations()
