Support without distutils (#2146)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Bernát Gábor 2021-07-14 17:45:30 +01:00 committed by GitHub
parent 0705f2050f
commit c52fe3ea09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 36 additions and 13 deletions

View file

@ -24,6 +24,7 @@ jobs:
- windows-latest
- macos-latest
py:
- 3.10.0-beta.4
- 3.9
- 3.8
- 3.7

View file

@ -42,7 +42,7 @@ repos:
rev: v1.17.0
hooks:
- id: setup-cfg-fmt
args: [--min-py3-version, "3.4"]
args: [--min-py3-version, "3.5", "--max-py-version", "3.10"]
- repo: https://github.com/PyCQA/flake8
rev: "3.9.2"
hooks:

View file

@ -0,0 +1,2 @@
Support Python interpreters without ``distutils`` (fallback to ``syconfig`` in these cases) - by :user:`gaborbernat`.

View file

@ -26,6 +26,7 @@ classifiers =
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: Implementation :: CPython
Programming Language :: Python :: Implementation :: PyPy
Topic :: Software Development :: Libraries

View file

@ -30,15 +30,15 @@ class Describe(object):
@property
def script_dir(self):
return self.dest / Path(self.interpreter.distutils_install["scripts"])
return self.dest / self.interpreter.install_path("scripts")
@property
def purelib(self):
return self.dest / self.interpreter.distutils_install["purelib"]
return self.dest / self.interpreter.install_path("purelib")
@property
def platlib(self):
return self.dest / self.interpreter.distutils_install["platlib"]
return self.dest / self.interpreter.install_path("platlib")
@property
def libs(self):

View file

@ -36,7 +36,7 @@ class CPython2(CPython, Python2):
@property
def include(self):
# the pattern include the distribution name too at the end, remove that via the parent call
return (self.dest / self.interpreter.distutils_install["headers"]).parent
return (self.dest / self.interpreter.install_path("headers")).parent
@classmethod
def modules(cls):

View file

@ -41,7 +41,7 @@ class PyPy2(PyPy, Python2):
@property
def include(self):
return self.dest / self.interpreter.distutils_install["headers"]
return self.dest / self.interpreter.install_path("headers")
@classmethod
def modules(cls):

View file

@ -12,9 +12,8 @@ import platform
import re
import sys
import sysconfig
import warnings
from collections import OrderedDict, namedtuple
from distutils import dist
from distutils.command.install import SCHEME_KEYS
from string import digits
VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"])
@ -118,10 +117,28 @@ class PythonInfo(object):
# note we must choose the original and not the pure executable as shim scripts might throw us off
return self.original_executable
def install_path(self, key):
result = self.distutils_install.get(key)
if result is None: # use sysconfig if distutils is unavailable
# set prefixes to empty => result is relative from cwd
prefixes = self.prefix, self.exec_prefix, self.base_prefix, self.base_exec_prefix
config_var = {k: "" if v in prefixes else v for k, v in self.sysconfig_vars}
result = self.sysconfig_path(key, config_var=config_var).lstrip(os.sep)
return result
@staticmethod
def _distutils_install():
# follow https://github.com/pypa/pip/blob/main/src/pip/_internal/locations.py#L95
# use distutils primarily because that's what pip does
# https://github.com/pypa/pip/blob/main/src/pip/_internal/locations.py#L95
# note here we don't import Distribution directly to allow setuptools to patch it
with warnings.catch_warnings(): # disable warning for PEP-632
warnings.simplefilter("ignore")
try:
from distutils import dist
from distutils.command.install import SCHEME_KEYS
except ImportError: # if removed or not installed ignore
return {}
d = dist.Distribution({"script_args": "--no-user-cfg"}) # conf files not parsed so they do not hijack paths
if hasattr(sys, "_framework"):
sys._framework = None # disable macOS static paths for framework
@ -177,7 +194,7 @@ class PythonInfo(object):
)
if not os.path.exists(path): # some broken packaging don't respect the sysconfig, fallback to distutils path
# the pattern include the distribution name too at the end, remove that via the parent call
fallback = os.path.join(self.prefix, os.path.dirname(self.distutils_install["headers"]))
fallback = os.path.join(self.prefix, os.path.dirname(self.install_path("headers")))
if os.path.exists(fallback):
path = fallback
return path

View file

@ -9,7 +9,9 @@ from virtualenv.activation import PowerShellActivator
@pytest.mark.slow
def test_powershell(activation_tester_class, activation_tester):
def test_powershell(activation_tester_class, activation_tester, monkeypatch):
monkeypatch.setenv("TERM", "xterm")
class PowerShell(activation_tester_class):
def __init__(self, session):
cmd = "powershell.exe" if sys.platform == "win32" else "pwsh"

View file

@ -17,7 +17,7 @@ def test_discover_empty_folder(tmp_path, monkeypatch, session_app_data):
CURRENT.discover_exe(session_app_data, prefix=str(tmp_path))
BASE = (CURRENT.distutils_install["scripts"], ".")
BASE = (CURRENT.install_path("scripts"), ".")
@pytest.mark.skipif(not fs_supports_symlink(), reason="symlink is not supported")

View file

@ -27,7 +27,7 @@ def test_discovery_via_path(monkeypatch, case, tmp_path, caplog, session_app_dat
elif case == "upper":
name = name.upper()
exe_name = "{}{}{}".format(name, current.version_info.major, ".exe" if sys.platform == "win32" else "")
target = tmp_path / current.distutils_install["scripts"]
target = tmp_path / current.install_path("scripts")
target.mkdir(parents=True)
executable = target / exe_name
os.symlink(sys.executable, ensure_text(str(executable)))