diff --git a/src/py_build_cmake/build.py b/src/py_build_cmake/build.py index 9778515..3e2011d 100644 --- a/src/py_build_cmake/build.py +++ b/src/py_build_cmake/build.py @@ -230,11 +230,12 @@ def create_wheel(self, wheel_directory, tmp_build_dir, cfg, dist_name, libdir = 'purelib' if pure else 'platlib' paths = {'prefix': str(tmp_build_dir), libdir: str(tmp_build_dir)} whl.dirname = wheel_directory - tags = None - if cfg.cross: - tags = self.get_cross_tags(cfg.cross) if pure: tags = {'pyver': ['py3']} + elif cfg.cross: + tags = self.get_cross_tags(cfg.cross) + else: + tags = self.get_native_tags() wheel_path = whl.build(paths, tags=tags, wheel_version=(1, 0)) whl_name = os.path.relpath(wheel_path, wheel_directory) return whl_name @@ -532,18 +533,25 @@ def get_cross_tags(crosscfg): 'arch': [crosscfg['arch']], } + @staticmethod + def get_native_tags(): + from .tags import get_python_tag, get_abi_tag, get_platform_tag + return { + 'pyver': [get_python_tag()], + 'abi': [get_abi_tag()], + 'arch': [get_platform_tag()], + } + @staticmethod def get_build_config_name(cross_cfg): """Get a string representing the Python version, ABI and architecture, used to name the build folder so builds for different versions don't interfere.""" if cross_cfg: - return '-'.join( - map(lambda x: x[0], - _BuildBackend.get_cross_tags(cross_cfg).values())) + tags = _BuildBackend.get_cross_tags(cross_cfg) else: - from distlib.wheel import IMPVER, ABI, ARCH - return '-'.join([IMPVER, ABI, ARCH]) + tags = _BuildBackend.get_native_tags() + return '-'.join(map(lambda x: x[0], tags.values())) def needs_cross_native_build(self, cfg): return cfg.cross and 'copy_from_native_build' in cfg.cross diff --git a/src/py_build_cmake/tags.py b/src/py_build_cmake/tags.py new file mode 100644 index 0000000..c38ce34 --- /dev/null +++ b/src/py_build_cmake/tags.py @@ -0,0 +1,83 @@ +""" +distlib.wheel doesn't always return the correct tags, and packaging.tags +returns all tags supported by the interpreter, not the tags that should be used +for the generated wheels. Therefore the only option here is to write our own +(kind of hacky) functions based on packaging.tags. +""" + +from typing import Dict +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from distlib.util import get_platform as get_platform_dashes + +_INTERPRETER_SHORT_NAMES: Dict[str, str] = { + "python": "py", + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +def _normalize_string(s: str) -> str: + return s.replace(".", "_").replace("-", "_") + + +def get_platform_tag() -> str: + return _normalize_string(get_platform_dashes()) + + +def get_interpreter_name() -> str: + name = sys.implementation.name + return _INTERPRETER_SHORT_NAMES.get(name) or name + + +def get_interpreter_version() -> str: + return "".join(map(str, sys.version_info[:2])) + + +def get_cpython_interpreter() -> str: + return f"cp{get_interpreter_version()}" + + +def get_cpython_abi() -> str: + """ + Get the ABI string for CPython, e.g. cp37m. + + https://github.com/pypa/packaging/blob/917612f5774571a99902b5fe04d06099b9e8b667/packaging/tags.py#L135 + """ + py_version = sys.version_info[:2] + debug = pymalloc = "" + with_debug = sysconfig.get_config_var("Py_DEBUG") + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + 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") + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + return f"{get_cpython_interpreter()}{debug}{pymalloc}" + + +def get_generic_interpreter() -> str: + return f"{get_interpreter_name()}{get_interpreter_version()}" + + +def get_generic_abi() -> str: + abi = sysconfig.get_config_var("SOABI") or "none" + return _normalize_string(abi) + + +def get_python_tag() -> str: + if get_interpreter_name() == "cp": return get_cpython_interpreter() + else: return get_generic_interpreter() + + +def get_abi_tag() -> str: + if get_interpreter_name() == "cp": return get_cpython_abi() + else: return get_generic_abi()