diff --git a/src/packaging/tags.py b/src/packaging/tags.py index a0e1ea23..19ccbde3 100644 --- a/src/packaging/tags.py +++ b/src/packaging/tags.py @@ -225,10 +225,45 @@ def cpython_tags( yield Tag(interpreter, "abi3", platform_) -def _generic_abi() -> Iterator[str]: - abi = sysconfig.get_config_var("SOABI") - if abi: - yield _normalize_string(abi) +def _generic_abi() -> List[str]: + """ + Return the ABI tag based on EXT_SUFFIX. + """ + # The following are examples of `EXT_SUFFIX`. + # We want to keep the parts which are related to the ABI and remove the + # parts which are related to the platform: + # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310 + # - mac: '.cpython-310-darwin.so' => cp310 + # - win: '.cp310-win_amd64.pyd' => cp310 + # - win: '.pyd' => cp37 (uses _cpython_abis()) + # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73 + # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib' + # => graalpy_38_native + + ext_suffix = _get_config_var("EXT_SUFFIX", warn=True) + if not isinstance(ext_suffix, str) or ext_suffix[0] != ".": + raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')") + parts = ext_suffix.split(".") + if len(parts) < 3: + # CPython3.7 and earlier uses ".pyd" on Windows. + return _cpython_abis(sys.version_info[:2]) + soabi = parts[1] + if soabi.startswith("cpython"): + # non-windows + abi = "cp" + soabi.split("-")[1] + elif soabi.startswith("cp"): + # windows + abi = soabi.split("-")[0] + elif soabi.startswith("pypy"): + abi = "-".join(soabi.split("-")[:2]) + elif soabi.startswith("graalpy"): + abi = "-".join(soabi.split("-")[:3]) + elif soabi: + # pyston, ironpython, others? + abi = soabi + else: + return [] + return [_normalize_string(abi)] def generic_tags( @@ -252,8 +287,9 @@ def generic_tags( interpreter = "".join([interp_name, interp_version]) if abis is None: abis = _generic_abi() + else: + abis = list(abis) platforms = list(platforms or platform_tags()) - abis = list(abis) if "none" not in abis: abis.append("none") for abi in abis: @@ -463,6 +499,9 @@ def platform_tags() -> Iterator[str]: def interpreter_name() -> str: """ Returns the name of the running interpreter. + + Some implementations have a reserved, two-letter abbreviation which will + be returned when appropriate. """ name = sys.implementation.name return INTERPRETER_SHORT_NAMES.get(name) or name diff --git a/tests/test_tags.py b/tests/test_tags.py index 39515e8d..34a0f146 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -823,22 +823,78 @@ def test_no_abi3_python27(self): class TestGenericTags: - @pytest.mark.skipif( - not sysconfig.get_config_var("SOABI"), reason="SOABI not defined" - ) - def test__generic_abi_soabi_provided(self): - abi = sysconfig.get_config_var("SOABI").replace(".", "_").replace("-", "_") - assert [abi] == list(tags._generic_abi()) - - def test__generic_abi(self, monkeypatch): + def test__generic_abi_macos(self, monkeypatch): monkeypatch.setattr( - sysconfig, "get_config_var", lambda key: "cpython-37m-darwin" + sysconfig, "get_config_var", lambda key: ".cpython-37m-darwin.so" ) - assert list(tags._generic_abi()) == ["cpython_37m_darwin"] + monkeypatch.setattr(tags, "interpreter_name", lambda: "cp") + assert tags._generic_abi() == ["cp37m"] + + def test__generic_abi_linux_cpython(self, monkeypatch): + config = { + "Py_DEBUG": False, + "WITH_PYMALLOC": True, + "EXT_SUFFIX": ".cpython-37m-x86_64-linux-gnu.so", + } + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + monkeypatch.setattr(tags, "interpreter_name", lambda: "cp") + # They are identical + assert tags._cpython_abis((3, 7)) == ["cp37m"] + assert tags._generic_abi() == ["cp37m"] + + def test__generic_abi_jp(self, monkeypatch): + config = {"EXT_SUFFIX": ".return exactly this.so"} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._generic_abi() == ["return exactly this"] + + def test__generic_abi_graal(self, monkeypatch): + config = {"EXT_SUFFIX": ".graalpy-38-native-x86_64-darwin.so"} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._generic_abi() == ["graalpy_38_native"] + + def test__generic_abi_none(self, monkeypatch): + config = {"EXT_SUFFIX": "..so"} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._generic_abi() == [] + + @pytest.mark.parametrize("ext_suffix", ["invalid", None]) + def test__generic_abi_error(self, ext_suffix, monkeypatch): + config = {"EXT_SUFFIX": ext_suffix} + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + with pytest.raises(SystemError) as e: + tags._generic_abi() + assert "EXT_SUFFIX" in str(e.value) + + def test__generic_abi_linux_pypy(self, monkeypatch): + # issue gh-606 + config = { + "Py_DEBUG": False, + "EXT_SUFFIX": ".pypy39-pp73-x86_64-linux-gnu.so", + } + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + monkeypatch.setattr(tags, "interpreter_name", lambda: "pp") + assert tags._generic_abi() == ["pypy39_pp73"] + + def test__generic_abi_old_windows(self, monkeypatch): + config = { + "EXT_SUFFIX": ".pyd", + "Py_DEBUG": 0, + "WITH_PYMALLOC": 0, + } + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._generic_abi() == tags._cpython_abis(sys.version_info[:2]) + + def test__generic_abi_windows(self, monkeypatch): + config = { + "EXT_SUFFIX": ".cp310-win_amd64.pyd", + } + monkeypatch.setattr(sysconfig, "get_config_var", config.__getitem__) + assert tags._generic_abi() == ["cp310"] - def test__generic_abi_no_soabi(self, monkeypatch): - monkeypatch.setattr(sysconfig, "get_config_var", lambda key: None) - assert not list(tags._generic_abi()) + @pytest.mark.skipif(sys.implementation.name != "cpython", reason="CPython-only") + def test__generic_abi_agree(self): + """Test that the two methods of finding the abi tag agree""" + assert tags._generic_abi() == tags._cpython_abis(sys.version_info[:2]) def test_generic_platforms(self): platform = sysconfig.get_platform().replace("-", "_") @@ -874,7 +930,7 @@ def test_interpreter_default(self, monkeypatch): assert result == [tags.Tag("sillywalkNN", "none", "any")] def test_abis_default(self, monkeypatch): - monkeypatch.setattr(tags, "_generic_abi", lambda: iter(["abi"])) + monkeypatch.setattr(tags, "_generic_abi", lambda: ["abi"]) result = list(tags.generic_tags(interpreter="sillywalk", platforms=["any"])) assert result == [ tags.Tag("sillywalk", "abi", "any"),