Skip to content

Commit

Permalink
More compatibility with pep425tags (#213)
Browse files Browse the repository at this point in the history
Merge pull request #213 from di/more-pip-compatibility
  • Loading branch information
pradyunsg authored Oct 2, 2019
2 parents 688e33e + 97dd2bd commit f36ecdf
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 18 deletions.
4 changes: 3 additions & 1 deletion docs/tags.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Reference
:param str tag: The tag to parse, e.g. ``"py3-none-any"``.


.. function:: sys_tags()
.. function:: sys_tags(warn=False)

Create an iterable of tags that the running interpreter supports.

Expand All @@ -94,6 +94,8 @@ Reference
short-circuiting of tag generation if the entire sequence is not necessary
and calculating some tags happens to be expensive.

:param bool warn: Whether warnings should be logged. Defaults to ``False``.


.. _abbreviation codes: https://www.python.org/dev/peps/pep-0425/#python-tag
.. _compressed tag set: https://www.python.org/dev/peps/pep-0425/#compressed-tag-sets
Expand Down
69 changes: 55 additions & 14 deletions packaging/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

EXTENSION_SUFFIXES = [x[0] for x in imp.get_suffixes()]
del imp
import logging
import os
import platform
import re
import sys
Expand All @@ -22,13 +24,15 @@
from ._typing import MYPY_CHECK_RUNNING, cast

if MYPY_CHECK_RUNNING: # pragma: no cover
from typing import Dict, FrozenSet, Iterable, Iterator, List, Optional, Tuple
from typing import Dict, FrozenSet, Iterable, Iterator, List, Optional, Tuple, Union

PythonVersion = Tuple[int, int]
MacVersion = Tuple[int, int]
GlibcVersion = Tuple[int, int]


logger = logging.getLogger(__name__)

INTERPRETER_SHORT_NAMES = {
"python": "py", # Generic.
"cpython": "cp",
Expand Down Expand Up @@ -101,6 +105,16 @@ def parse_tag(tag):
return frozenset(tags)


def _get_config_var(name, warn=False):
# type: (str, Optional[bool]) -> Union[int, str, None]
value = sysconfig.get_config_var(name)
if value is None and warn:
logger.debug(
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
)
return value


def _normalize_string(string):
# type: (str) -> str
return string.replace(".", "_").replace("-", "_")
Expand All @@ -112,12 +126,12 @@ def _cpython_interpreter(py_version):
return "cp{major}{minor}".format(major=py_version[0], minor=py_version[1])


def _cpython_abis(py_version):
# type: (PythonVersion) -> List[str]
def _cpython_abis(py_version, warn=False):
# type: (PythonVersion, Optional[bool]) -> List[str]
abis = []
version = "{}{}".format(*py_version[:2])
debug = pymalloc = ucs4 = ""
with_debug = sysconfig.get_config_var("Py_DEBUG")
with_debug = _get_config_var("Py_DEBUG", warn)
has_refcount = hasattr(sys, "gettotalrefcount")
# Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
# extension modules is the best option.
Expand All @@ -126,11 +140,11 @@ def _cpython_abis(py_version):
if with_debug or (with_debug is None and (has_refcount or has_ext)):
debug = "d"
if py_version < (3, 8):
with_pymalloc = sysconfig.get_config_var("WITH_PYMALLOC")
with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
if with_pymalloc or with_pymalloc is None:
pymalloc = "m"
if py_version < (3, 3):
unicode_size = sysconfig.get_config_var("Py_UNICODE_SIZE")
unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
if unicode_size == 4 or (
unicode_size is None and sys.maxunicode == 0x10FFFF
):
Expand Down Expand Up @@ -322,7 +336,34 @@ def _is_manylinux_compatible(name, glibc_version):
def _glibc_version_string():
# type: () -> Optional[str]
# Returns glibc version string, or None if not using glibc.
import ctypes
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()


def _glibc_version_string_confstr():
# type: () -> Optional[str]
"Primary implementation of glibc_version_string using os.confstr."
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
# to be broken or missing. This strategy is used in the standard library
# platform module.
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c9d0921ff3d70e1127ca1b71/Lib/platform.py#L175-L183
try:
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17".
version_string = os.confstr("CS_GNU_LIBC_VERSION")
assert version_string is not None
_, version = version_string.split()
except (AssertionError, AttributeError, OSError, ValueError):
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
return None
return version


def _glibc_version_string_ctypes():
# type: () -> Optional[str]
"Fallback implementation of glibc_version_string using ctypes."
try:
import ctypes
except ImportError:
return None

# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
# manpage says, "If filename is NULL, then the returned handle is for the
Expand Down Expand Up @@ -414,16 +455,16 @@ def _interpreter_name():
return INTERPRETER_SHORT_NAMES.get(name) or name


def _generic_interpreter(name, py_version):
# type: (str, PythonVersion) -> str
version = sysconfig.get_config_var("py_version_nodot")
def _generic_interpreter(name, py_version, warn=False):
# type: (str, PythonVersion, Optional[bool]) -> str
version = _get_config_var("py_version_nodot", warn)
if not version:
version = "".join(map(str, py_version[:2]))
return "{name}{version}".format(name=name, version=version)


def sys_tags():
# type: () -> Iterator[Tag]
def sys_tags(warn=False):
# type: (Optional[bool]) -> Iterator[Tag]
"""
Returns the sequence of tag triples for the running interpreter.
Expand All @@ -441,7 +482,7 @@ def sys_tags():

if interpreter_name == "cp":
interpreter = _cpython_interpreter(py_version)
abis = _cpython_abis(py_version)
abis = _cpython_abis(py_version, warn)
for tag in _cpython_tags(py_version, interpreter, abis, platforms):
yield tag
elif interpreter_name == "pp":
Expand All @@ -450,7 +491,7 @@ def sys_tags():
for tag in _pypy_tags(py_version, interpreter, abi, platforms):
yield tag
else:
interpreter = _generic_interpreter(interpreter_name, py_version)
interpreter = _generic_interpreter(interpreter_name, py_version, warn)
abi = _generic_abi()
for tag in _generic_tags(interpreter, py_version, abi, platforms):
yield tag
Expand Down
50 changes: 47 additions & 3 deletions tests/test_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
ctypes = None
import distutils.util

import os
import platform
import re
import sys
import sysconfig
import types
import warnings

import pretend
import pytest

from packaging import tags
Expand Down Expand Up @@ -324,7 +326,7 @@ def test_cpython_tags():
def test_sys_tags_on_mac_cpython(monkeypatch):
if platform.python_implementation() != "CPython":
monkeypatch.setattr(platform, "python_implementation", lambda: "CPython")
monkeypatch.setattr(tags, "_cpython_abis", lambda py_version: ["cp33m"])
monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"])
if platform.system() != "Darwin":
monkeypatch.setattr(platform, "system", lambda: "Darwin")
monkeypatch.setattr(tags, "_mac_platforms", lambda: ["macosx_10_5_x86_64"])
Expand Down Expand Up @@ -437,7 +439,7 @@ def test_generic_tags():
def test_sys_tags_on_windows_cpython(monkeypatch):
if platform.python_implementation() != "CPython":
monkeypatch.setattr(platform, "python_implementation", lambda: "CPython")
monkeypatch.setattr(tags, "_cpython_abis", lambda py_version: ["cp33m"])
monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"])
if platform.system() != "Windows":
monkeypatch.setattr(platform, "system", lambda: "Windows")
monkeypatch.setattr(tags, "_generic_platforms", lambda: ["win_amd64"])
Expand Down Expand Up @@ -523,13 +525,55 @@ def __init__(self, libc_version):

process_namespace = ProcessNamespace(LibcVersion(version_str))
monkeypatch.setattr(ctypes, "CDLL", lambda _: process_namespace)
monkeypatch.setattr(tags, "_glibc_version_string_confstr", lambda: False)

assert tags._glibc_version_string() == expected

del process_namespace.gnu_get_libc_version
assert tags._glibc_version_string() is None


def test_glibc_version_string_confstr(monkeypatch):
monkeypatch.setattr(os, "confstr", lambda x: "glibc 2.20", raising=False)
assert tags._glibc_version_string_confstr() == "2.20"


@pytest.mark.parametrize(
"failure", [pretend.raiser(ValueError), pretend.raiser(OSError), lambda x: "XXX"]
)
def test_glibc_version_string_confstr_fail(monkeypatch, failure):
monkeypatch.setattr(os, "confstr", failure, raising=False)
assert tags._glibc_version_string_confstr() is None


def test_glibc_version_string_confstr_missing(monkeypatch):
monkeypatch.delattr(os, "confstr", raising=False)
assert tags._glibc_version_string_confstr() is None


def test_glibc_version_string_ctypes_missing(monkeypatch):
monkeypatch.setitem(sys.modules, "ctypes", None)
assert tags._glibc_version_string_ctypes() is None


def test_get_config_var_does_not_log(monkeypatch):
debug = pretend.call_recorder(lambda *a: None)
monkeypatch.setattr(tags.logger, "debug", debug)
tags._get_config_var("missing")
assert debug.calls == []


def test_get_config_var_does_log(monkeypatch):
debug = pretend.call_recorder(lambda *a: None)
monkeypatch.setattr(tags.logger, "debug", debug)
tags._get_config_var("missing", warn=True)
assert debug.calls == [
pretend.call(
"Config variable '%s' is unset, Python ABI tag may be incorrect", "missing"
)
]


def test_have_compatible_glibc(monkeypatch):
if platform.system() == "Linux":
# Assuming no one is running this test with a version of glibc released in
Expand Down Expand Up @@ -608,7 +652,7 @@ def test_linux_platforms_manylinux2014(monkeypatch):
def test_sys_tags_linux_cpython(monkeypatch):
if platform.python_implementation() != "CPython":
monkeypatch.setattr(platform, "python_implementation", lambda: "CPython")
monkeypatch.setattr(tags, "_cpython_abis", lambda py_version: ["cp33m"])
monkeypatch.setattr(tags, "_cpython_abis", lambda *a: ["cp33m"])
if platform.system() != "Linux":
monkeypatch.setattr(platform, "system", lambda: "Linux")
monkeypatch.setattr(tags, "_linux_platforms", lambda: ["linux_x86_64"])
Expand Down

0 comments on commit f36ecdf

Please sign in to comment.