From 7f2e494b468d351a62f1d8f691d237b92c7aabcd Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Sat, 11 Oct 2025 17:00:12 -0500 Subject: [PATCH 01/10] [ABI Dependency] Best-Effort support for `pip` --- variantlib/constants.py | 2 + variantlib/resolver/lib.py | 80 +++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/variantlib/constants.py b/variantlib/constants.py index 2fb2a0d..604bb00 100644 --- a/variantlib/constants.py +++ b/variantlib/constants.py @@ -65,6 +65,8 @@ ) VALIDATION_PROVIDER_REQUIRES_REGEX = re.compile(r"[\S ]+") +VARIANT_ABI_DEPENDENCY_NAMESPACE: Literal["abi_dependency"] = "abi_dependency" + # VALIDATION_PYTHON_PACKAGE_NAME_REGEX = re.compile(r"[^\s-]+?") # Per PEP 508: https://peps.python.org/pep-0508/#names diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index 7cc00a8..f60fc1e 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -1,7 +1,13 @@ from __future__ import annotations +import logging +import os +from importlib import metadata from typing import TYPE_CHECKING +from packaging.utils import canonicalize_name + +from variantlib.constants import VARIANT_ABI_DEPENDENCY_NAMESPACE from variantlib.models.variant import VariantDescription from variantlib.models.variant import VariantFeature from variantlib.models.variant import VariantProperty @@ -20,6 +26,18 @@ from variantlib.protocols import VariantFeatureValue from variantlib.protocols import VariantNamespace +logger = logging.getLogger(__name__) + + +def _normalize_package_name(name: str) -> str: + # VALIDATION_FEATURE_NAME_REGEX does not accepts "-" + return canonicalize_name(name).replace("-", "_") + + +def _normalize_package_version(version: str) -> str: + # VALIDATION_VALUE_REGEX does not accepts "+" + return version.split("+", maxsplit=1)[0] + def filter_variants( vdescs: list[VariantDescription], @@ -124,8 +142,62 @@ def sort_and_filter_supported_variants( :param property_priorities: Ordered list of `VariantProperty` objects. :return: Sorted and filtered list of `VariantDescription` objects. """ + validate_type(vdescs, list[VariantDescription]) + validate_type(supported_vprops, list[VariantProperty]) + if namespace_priorities is None: + namespace_priorities = [] + + # ======================================================================= # + # ABI DEPENDENCY INJECTION # + # ======================================================================= # + + # 1. Manually fed from environment variable + # Note: come first for "implicit higher priority" + # Env Var Format: `VARIANT_ABI_DEPENDENCY=packageA==1.2.3,...,packageZ==7.8.9` + if variant_abi_deps_env := os.environ.get("NV_VARIANT_PROVIDER_FORCE_SM_ARCH"): + for pkg_spec in variant_abi_deps_env.split(","): + try: + pkg_name, pkg_version = pkg_spec.split("==", maxsplit=1) + except ValueError: + logger.exception( + "`NV_VARIANT_PROVIDER_FORCE_SM_ARCH` received an invalid value " + "`%(pkg_spec)s`. It will be ignored.\n" + "Expected format: `packageA==1.2.3,...,packageZ==7.8.9`.", + {"pkg_spec": pkg_spec}, + ) + continue + + supported_vprops.append( + VariantProperty( + namespace=VARIANT_ABI_DEPENDENCY_NAMESPACE, + feature=_normalize_package_name(pkg_name), + value=_normalize_package_version(pkg_version), + ) + ) + + # 2. Automatically populate from the current python environment + packages = [ + (dist.metadata["Name"], dist.version) for dist in metadata.distributions() + ] + for pkg_name, pkg_version in sorted(packages): + supported_vprops.append( + VariantProperty( + namespace=VARIANT_ABI_DEPENDENCY_NAMESPACE, + feature=_normalize_package_name(pkg_name), + value=_normalize_package_version(pkg_version), + ) + ) + + # 3. Adding `VARIANT_ABI_DEPENDENCY_NAMESPACE` at the back of`namespace_priorities` + namespace_priorities.append(VARIANT_ABI_DEPENDENCY_NAMESPACE) + + # ======================================================================= # + # NULL VARIANT # + # ======================================================================= # + + # Adding the `null-variant` to the list - always "compatible" if (null_variant := VariantDescription()) not in vdescs: """Add a null variant description to the list.""" # This is needed to ensure that we always consider the null variant @@ -139,7 +211,9 @@ def sort_and_filter_supported_variants( """No supported properties provided, return no variants.""" return [] - validate_type(supported_vprops, list[VariantProperty]) + # ======================================================================= # + # FILTERING # + # ======================================================================= # # Step 1: we remove any duplicate, or unsupported `VariantDescription` on # this platform. @@ -153,6 +227,10 @@ def sort_and_filter_supported_variants( ) ) + # ======================================================================= # + # SORTING # + # ======================================================================= # + # Step 2: we sort the supported `VariantProperty`s based on their respective # priority. sorted_supported_vprops = sort_variant_properties( From 9a4f968bfa4da989a78f89fdbba670e7fb61a385 Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Sun, 12 Oct 2025 13:03:07 -0500 Subject: [PATCH 02/10] Support Major / Minor / Micro level ABI Compatibility --- variantlib/resolver/lib.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index f60fc1e..f7215cb 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING from packaging.utils import canonicalize_name +from packaging.version import Version from variantlib.constants import VARIANT_ABI_DEPENDENCY_NAMESPACE from variantlib.models.variant import VariantDescription @@ -34,9 +35,11 @@ def _normalize_package_name(name: str) -> str: return canonicalize_name(name).replace("-", "_") -def _normalize_package_version(version: str) -> str: - # VALIDATION_VALUE_REGEX does not accepts "+" - return version.split("+", maxsplit=1)[0] +def _generate_version_matches(version: str) -> Generator[str]: + vspec = Version(version) + yield f"{vspec.major}" + yield f"{vspec.major}.{vspec.minor}" + yield f"{vspec.major}.{vspec.minor}.{vspec.micro}" def filter_variants( @@ -156,25 +159,26 @@ def sort_and_filter_supported_variants( # 1. Manually fed from environment variable # Note: come first for "implicit higher priority" # Env Var Format: `VARIANT_ABI_DEPENDENCY=packageA==1.2.3,...,packageZ==7.8.9` - if variant_abi_deps_env := os.environ.get("NV_VARIANT_PROVIDER_FORCE_SM_ARCH"): + if variant_abi_deps_env := os.environ.get("VARIANT_ABI_DEPENDENCY"): for pkg_spec in variant_abi_deps_env.split(","): try: pkg_name, pkg_version = pkg_spec.split("==", maxsplit=1) except ValueError: logger.exception( - "`NV_VARIANT_PROVIDER_FORCE_SM_ARCH` received an invalid value " + "`VARIANT_ABI_DEPENDENCY` received an invalid value " "`%(pkg_spec)s`. It will be ignored.\n" "Expected format: `packageA==1.2.3,...,packageZ==7.8.9`.", {"pkg_spec": pkg_spec}, ) continue - supported_vprops.append( + supported_vprops.extend( VariantProperty( namespace=VARIANT_ABI_DEPENDENCY_NAMESPACE, feature=_normalize_package_name(pkg_name), - value=_normalize_package_version(pkg_version), + value=_ver, ) + for _ver in _generate_version_matches(pkg_version) ) # 2. Automatically populate from the current python environment @@ -182,12 +186,13 @@ def sort_and_filter_supported_variants( (dist.metadata["Name"], dist.version) for dist in metadata.distributions() ] for pkg_name, pkg_version in sorted(packages): - supported_vprops.append( + supported_vprops.extend( VariantProperty( namespace=VARIANT_ABI_DEPENDENCY_NAMESPACE, feature=_normalize_package_name(pkg_name), - value=_normalize_package_version(pkg_version), + value=_ver, ) + for _ver in _generate_version_matches(pkg_version) ) # 3. Adding `VARIANT_ABI_DEPENDENCY_NAMESPACE` at the back of`namespace_priorities` From 4e3c68508f80151872b57b983739584c724c5072 Mon Sep 17 00:00:00 2001 From: Jonathan DEKHTIAR Date: Mon, 13 Oct 2025 13:08:38 -0500 Subject: [PATCH 03/10] Update variantlib/resolver/lib.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Górny --- variantlib/resolver/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index f7215cb..91e9db1 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -183,7 +183,7 @@ def sort_and_filter_supported_variants( # 2. Automatically populate from the current python environment packages = [ - (dist.metadata["Name"], dist.version) for dist in metadata.distributions() + (dist.name, dist.version) for dist in metadata.distributions() ] for pkg_name, pkg_version in sorted(packages): supported_vprops.extend( From 90caea7d9992c446ec48b26ed6a7c82262bc4572 Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Mon, 13 Oct 2025 13:12:00 -0500 Subject: [PATCH 04/10] Fix PR comments --- variantlib/resolver/lib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index 91e9db1..1b14e39 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -152,6 +152,10 @@ def sort_and_filter_supported_variants( if namespace_priorities is None: namespace_priorities = [] + # Avoiding modification in place + namespace_priorities = namespace_priorities.copy() + supported_vprops = supported_vprops.copy() + # ======================================================================= # # ABI DEPENDENCY INJECTION # # ======================================================================= # From 42e6cbe7e6802df7b7fd1851ef1b6f411766f98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 13 Nov 2025 15:54:06 +0100 Subject: [PATCH 05/10] Move abi_dependency injection into a dedicated function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/resolver/lib.py | 87 ++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index 1b14e39..2afb393 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -121,44 +121,11 @@ def filter_variants( yield from result -def sort_and_filter_supported_variants( - vdescs: list[VariantDescription], +def inject_abi_dependency( supported_vprops: list[VariantProperty], - namespace_priorities: list[VariantNamespace] | None = None, - feature_priorities: dict[VariantNamespace, list[VariantFeatureName]] | None = None, - property_priorities: dict[ - VariantNamespace, dict[VariantFeatureName, list[VariantFeatureValue]] - ] - | None = None, - forbidden_namespaces: list[VariantNamespace] | None = None, - forbidden_features: list[VariantFeature] | None = None, - forbidden_properties: list[VariantProperty] | None = None, -) -> list[VariantDescription]: - """ - Sort and filter a list of `VariantDescription` objects based on their - `VariantProperty`s. - - :param vdescs: List of `VariantDescription` objects. - :param supported_vprops: List of `VariantProperty` objects supported on the platform - :param namespace_priorities: Ordered list of `str` objects. - :param feature_priorities: Ordered list of `VariantFeature` objects. - :param property_priorities: Ordered list of `VariantProperty` objects. - :return: Sorted and filtered list of `VariantDescription` objects. - """ - - validate_type(vdescs, list[VariantDescription]) - validate_type(supported_vprops, list[VariantProperty]) - - if namespace_priorities is None: - namespace_priorities = [] - - # Avoiding modification in place - namespace_priorities = namespace_priorities.copy() - supported_vprops = supported_vprops.copy() - - # ======================================================================= # - # ABI DEPENDENCY INJECTION # - # ======================================================================= # + namespace_priorities: list[VariantNamespace], +) -> None: + """Inject supported vairants for the abi_dependency namespace""" # 1. Manually fed from environment variable # Note: come first for "implicit higher priority" @@ -186,9 +153,7 @@ def sort_and_filter_supported_variants( ) # 2. Automatically populate from the current python environment - packages = [ - (dist.name, dist.version) for dist in metadata.distributions() - ] + packages = [(dist.name, dist.version) for dist in metadata.distributions()] for pkg_name, pkg_version in sorted(packages): supported_vprops.extend( VariantProperty( @@ -202,6 +167,48 @@ def sort_and_filter_supported_variants( # 3. Adding `VARIANT_ABI_DEPENDENCY_NAMESPACE` at the back of`namespace_priorities` namespace_priorities.append(VARIANT_ABI_DEPENDENCY_NAMESPACE) + +def sort_and_filter_supported_variants( + vdescs: list[VariantDescription], + supported_vprops: list[VariantProperty], + namespace_priorities: list[VariantNamespace] | None = None, + feature_priorities: dict[VariantNamespace, list[VariantFeatureName]] | None = None, + property_priorities: dict[ + VariantNamespace, dict[VariantFeatureName, list[VariantFeatureValue]] + ] + | None = None, + forbidden_namespaces: list[VariantNamespace] | None = None, + forbidden_features: list[VariantFeature] | None = None, + forbidden_properties: list[VariantProperty] | None = None, +) -> list[VariantDescription]: + """ + Sort and filter a list of `VariantDescription` objects based on their + `VariantProperty`s. + + :param vdescs: List of `VariantDescription` objects. + :param supported_vprops: List of `VariantProperty` objects supported on the platform + :param namespace_priorities: Ordered list of `str` objects. + :param feature_priorities: Ordered list of `VariantFeature` objects. + :param property_priorities: Ordered list of `VariantProperty` objects. + :return: Sorted and filtered list of `VariantDescription` objects. + """ + + validate_type(vdescs, list[VariantDescription]) + validate_type(supported_vprops, list[VariantProperty]) + + if namespace_priorities is None: + namespace_priorities = [] + + # Avoiding modification in place + namespace_priorities = namespace_priorities.copy() + supported_vprops = supported_vprops.copy() + + # ======================================================================= # + # ABI DEPENDENCY INJECTION # + # ======================================================================= # + + inject_abi_dependency(supported_vprops, namespace_priorities) + # ======================================================================= # # NULL VARIANT # # ======================================================================= # From 5a5bf17edd3998fa88c83f418b579149f030847c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 13 Nov 2025 16:22:02 +0100 Subject: [PATCH 06/10] Add a test for the current implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/resolver/test_abi_dependency.py | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/resolver/test_abi_dependency.py diff --git a/tests/resolver/test_abi_dependency.py b/tests/resolver/test_abi_dependency.py new file mode 100644 index 0000000..f3ab006 --- /dev/null +++ b/tests/resolver/test_abi_dependency.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from variantlib.constants import VARIANT_ABI_DEPENDENCY_NAMESPACE +from variantlib.models.variant import VariantProperty +from variantlib.resolver.lib import inject_abi_dependency + +if TYPE_CHECKING: + import pytest + import pytest_mock + + +@dataclass +class MockedDistribution: + name: str + version: str + + +def test_inject_abi_dependency( + monkeypatch: pytest.MonkeyPatch, mocker: pytest_mock.MockerFixture +) -> None: + monkeypatch.delenv("VARIANT_ABI_DEPENDENCY", raising=False) + + namespace_priorities = ["foo"] + supported_vprops = [ + VariantProperty("foo", "bar", "baz"), + ] + + mocker.patch("importlib.metadata.distributions").return_value = [ + MockedDistribution("a", "4"), + MockedDistribution("b", "4.3b1"), + MockedDistribution("c", "7.2.3.post4"), + MockedDistribution("d", "1.2.3.4"), + ] + inject_abi_dependency(supported_vprops, namespace_priorities) + + assert namespace_priorities == ["foo", VARIANT_ABI_DEPENDENCY_NAMESPACE] + assert supported_vprops == [ + VariantProperty("foo", "bar", "baz"), + VariantProperty("abi_dependency", "a", "4"), + VariantProperty("abi_dependency", "a", "4.0"), + VariantProperty("abi_dependency", "a", "4.0.0"), + VariantProperty("abi_dependency", "b", "4"), + VariantProperty("abi_dependency", "b", "4.3"), + VariantProperty("abi_dependency", "b", "4.3.0"), + VariantProperty("abi_dependency", "c", "7"), + VariantProperty("abi_dependency", "c", "7.2"), + VariantProperty("abi_dependency", "c", "7.2.3"), + VariantProperty("abi_dependency", "d", "1"), + VariantProperty("abi_dependency", "d", "1.2"), + VariantProperty("abi_dependency", "d", "1.2.3"), + ] From 276847c049460f6cede3b427b41ba2b2862fbc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 13 Nov 2025 16:51:13 +0100 Subject: [PATCH 07/10] Add tests for envvar overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/resolver/test_abi_dependency.py | 64 ++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/tests/resolver/test_abi_dependency.py b/tests/resolver/test_abi_dependency.py index f3ab006..f5ee28a 100644 --- a/tests/resolver/test_abi_dependency.py +++ b/tests/resolver/test_abi_dependency.py @@ -3,13 +3,14 @@ from dataclasses import dataclass from typing import TYPE_CHECKING +import pytest from variantlib.constants import VARIANT_ABI_DEPENDENCY_NAMESPACE from variantlib.models.variant import VariantProperty from variantlib.resolver.lib import inject_abi_dependency if TYPE_CHECKING: - import pytest import pytest_mock + from variantlib.protocols import VariantNamespace @dataclass @@ -52,3 +53,64 @@ def test_inject_abi_dependency( VariantProperty("abi_dependency", "d", "1.2"), VariantProperty("abi_dependency", "d", "1.2.3"), ] + + +@pytest.mark.parametrize( + "env_value", + [ + "", + "c==7.8.9b1", + "d==4.9.4,c==7.8.9", + "a==1.2.79", + "a==1.2.79,d==4.9.4", + # invalid components should be ignored (with a warning) + "no-version", + "a==1.2.79,no-version", + "z>=1.2.3", + ], +) +def test_inject_abi_dependency_envvar( + monkeypatch: pytest.MonkeyPatch, + mocker: pytest_mock.MockerFixture, + env_value: str, +) -> None: + monkeypatch.setenv("VARIANT_ABI_DEPENDENCY", env_value) + + namespace_priorities: list[VariantNamespace] = [] + supported_vprops: list[VariantProperty] = [] + + mocker.patch("importlib.metadata.distributions").return_value = [ + MockedDistribution("a", "1.2.3"), + MockedDistribution("b", "4.7.9"), + ] + inject_abi_dependency(supported_vprops, namespace_priorities) + + expected = { + VariantProperty("abi_dependency", "a", "1"), + VariantProperty("abi_dependency", "a", "1.2"), + VariantProperty("abi_dependency", "a", "1.2.3"), + VariantProperty("abi_dependency", "b", "4"), + VariantProperty("abi_dependency", "b", "4.7"), + VariantProperty("abi_dependency", "b", "4.7.9"), + } + if "a" in env_value: + expected |= { + VariantProperty("abi_dependency", "a", "1"), + VariantProperty("abi_dependency", "a", "1.2"), + VariantProperty("abi_dependency", "a", "1.2.79"), + } + if "c" in env_value: + expected |= { + VariantProperty("abi_dependency", "c", "7"), + VariantProperty("abi_dependency", "c", "7.8"), + VariantProperty("abi_dependency", "c", "7.8.9"), + } + if "d" in env_value: + expected |= { + VariantProperty("abi_dependency", "d", "4"), + VariantProperty("abi_dependency", "d", "4.9"), + VariantProperty("abi_dependency", "d", "4.9.4"), + } + + assert namespace_priorities == [VARIANT_ABI_DEPENDENCY_NAMESPACE] + assert set(supported_vprops) == expected From 5f336fc8430ff1f80c485838d800e1bb6479aa55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 13 Nov 2025 16:58:20 +0100 Subject: [PATCH 08/10] Make envvar override installed package versions explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/resolver/test_abi_dependency.py | 11 ++++++---- variantlib/resolver/lib.py | 30 +++++++++++++++------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/tests/resolver/test_abi_dependency.py b/tests/resolver/test_abi_dependency.py index f5ee28a..57aaeb8 100644 --- a/tests/resolver/test_abi_dependency.py +++ b/tests/resolver/test_abi_dependency.py @@ -86,14 +86,17 @@ def test_inject_abi_dependency_envvar( inject_abi_dependency(supported_vprops, namespace_priorities) expected = { - VariantProperty("abi_dependency", "a", "1"), - VariantProperty("abi_dependency", "a", "1.2"), - VariantProperty("abi_dependency", "a", "1.2.3"), VariantProperty("abi_dependency", "b", "4"), VariantProperty("abi_dependency", "b", "4.7"), VariantProperty("abi_dependency", "b", "4.7.9"), } - if "a" in env_value: + if "a" not in env_value: + expected |= { + VariantProperty("abi_dependency", "a", "1"), + VariantProperty("abi_dependency", "a", "1.2"), + VariantProperty("abi_dependency", "a", "1.2.3"), + } + else: expected |= { VariantProperty("abi_dependency", "a", "1"), VariantProperty("abi_dependency", "a", "1.2"), diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index 2afb393..4a35388 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -127,15 +127,17 @@ def inject_abi_dependency( ) -> None: """Inject supported vairants for the abi_dependency namespace""" - # 1. Manually fed from environment variable - # Note: come first for "implicit higher priority" + # 1. Automatically populate from the current python environment + packages = {dist.name: dist.version for dist in metadata.distributions()} + + # 2. Manually fed from environment variable # Env Var Format: `VARIANT_ABI_DEPENDENCY=packageA==1.2.3,...,packageZ==7.8.9` if variant_abi_deps_env := os.environ.get("VARIANT_ABI_DEPENDENCY"): for pkg_spec in variant_abi_deps_env.split(","): try: pkg_name, pkg_version = pkg_spec.split("==", maxsplit=1) except ValueError: - logger.exception( + logger.warning( "`VARIANT_ABI_DEPENDENCY` received an invalid value " "`%(pkg_spec)s`. It will be ignored.\n" "Expected format: `packageA==1.2.3,...,packageZ==7.8.9`.", @@ -143,18 +145,20 @@ def inject_abi_dependency( ) continue - supported_vprops.extend( - VariantProperty( - namespace=VARIANT_ABI_DEPENDENCY_NAMESPACE, - feature=_normalize_package_name(pkg_name), - value=_ver, + if (old_version := packages.get(pkg_name)) is not None: + logger.warning( + "`VARIANT_ABI_DEPENDENCY` overrides package version: " + "`%(pkg_name)s` from `%(old_ver)s` to `%(new_ver)s`", + { + "pkg_name": pkg_name, + "old_ver": old_version, + "new_ver": pkg_version, + }, ) - for _ver in _generate_version_matches(pkg_version) - ) - # 2. Automatically populate from the current python environment - packages = [(dist.name, dist.version) for dist in metadata.distributions()] - for pkg_name, pkg_version in sorted(packages): + packages[pkg_name] = pkg_version + + for pkg_name, pkg_version in sorted(packages.items()): supported_vprops.extend( VariantProperty( namespace=VARIANT_ABI_DEPENDENCY_NAMESPACE, From 6fe4a03fc2832de6c2192ee612c3108b29afa46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 13 Nov 2025 17:00:57 +0100 Subject: [PATCH 09/10] Import `importlib.metadata` using full name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- variantlib/resolver/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index 4a35388..75932b2 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -1,8 +1,8 @@ from __future__ import annotations +import importlib.metadata import logging import os -from importlib import metadata from typing import TYPE_CHECKING from packaging.utils import canonicalize_name @@ -128,7 +128,7 @@ def inject_abi_dependency( """Inject supported vairants for the abi_dependency namespace""" # 1. Automatically populate from the current python environment - packages = {dist.name: dist.version for dist in metadata.distributions()} + packages = {dist.name: dist.version for dist in importlib.metadata.distributions()} # 2. Manually fed from environment variable # Env Var Format: `VARIANT_ABI_DEPENDENCY=packageA==1.2.3,...,packageZ==7.8.9` From f530dcab07db7a27e2739e3863d43f9e95f3d4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 13 Nov 2025 20:04:59 +0100 Subject: [PATCH 10/10] Normalize package names earlier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/resolver/test_abi_dependency.py | 47 ++++++++++++++------------- variantlib/resolver/lib.py | 8 +++-- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/tests/resolver/test_abi_dependency.py b/tests/resolver/test_abi_dependency.py index 57aaeb8..7bb6ab5 100644 --- a/tests/resolver/test_abi_dependency.py +++ b/tests/resolver/test_abi_dependency.py @@ -32,8 +32,8 @@ def test_inject_abi_dependency( mocker.patch("importlib.metadata.distributions").return_value = [ MockedDistribution("a", "4"), MockedDistribution("b", "4.3b1"), - MockedDistribution("c", "7.2.3.post4"), - MockedDistribution("d", "1.2.3.4"), + MockedDistribution("c.ns", "7.2.3.post4"), + MockedDistribution("d-foo", "1.2.3.4"), ] inject_abi_dependency(supported_vprops, namespace_priorities) @@ -46,12 +46,12 @@ def test_inject_abi_dependency( VariantProperty("abi_dependency", "b", "4"), VariantProperty("abi_dependency", "b", "4.3"), VariantProperty("abi_dependency", "b", "4.3.0"), - VariantProperty("abi_dependency", "c", "7"), - VariantProperty("abi_dependency", "c", "7.2"), - VariantProperty("abi_dependency", "c", "7.2.3"), - VariantProperty("abi_dependency", "d", "1"), - VariantProperty("abi_dependency", "d", "1.2"), - VariantProperty("abi_dependency", "d", "1.2.3"), + VariantProperty("abi_dependency", "c_ns", "7"), + VariantProperty("abi_dependency", "c_ns", "7.2"), + VariantProperty("abi_dependency", "c_ns", "7.2.3"), + VariantProperty("abi_dependency", "d_foo", "1"), + VariantProperty("abi_dependency", "d_foo", "1.2"), + VariantProperty("abi_dependency", "d_foo", "1.2.3"), ] @@ -59,13 +59,14 @@ def test_inject_abi_dependency( "env_value", [ "", - "c==7.8.9b1", - "d==4.9.4,c==7.8.9", - "a==1.2.79", - "a==1.2.79,d==4.9.4", + "c_ns==7.8.9b1", + "c.ns==7.8.9b1", + "d==4.9.4,c.ns==7.8.9", + "a_foo==1.2.79", + "a-foo==1.2.79,d==4.9.4", # invalid components should be ignored (with a warning) "no-version", - "a==1.2.79,no-version", + "a.foo==1.2.79,no-version", "z>=1.2.3", ], ) @@ -80,7 +81,7 @@ def test_inject_abi_dependency_envvar( supported_vprops: list[VariantProperty] = [] mocker.patch("importlib.metadata.distributions").return_value = [ - MockedDistribution("a", "1.2.3"), + MockedDistribution("a-foo", "1.2.3"), MockedDistribution("b", "4.7.9"), ] inject_abi_dependency(supported_vprops, namespace_priorities) @@ -92,21 +93,21 @@ def test_inject_abi_dependency_envvar( } if "a" not in env_value: expected |= { - VariantProperty("abi_dependency", "a", "1"), - VariantProperty("abi_dependency", "a", "1.2"), - VariantProperty("abi_dependency", "a", "1.2.3"), + VariantProperty("abi_dependency", "a_foo", "1"), + VariantProperty("abi_dependency", "a_foo", "1.2"), + VariantProperty("abi_dependency", "a_foo", "1.2.3"), } else: expected |= { - VariantProperty("abi_dependency", "a", "1"), - VariantProperty("abi_dependency", "a", "1.2"), - VariantProperty("abi_dependency", "a", "1.2.79"), + VariantProperty("abi_dependency", "a_foo", "1"), + VariantProperty("abi_dependency", "a_foo", "1.2"), + VariantProperty("abi_dependency", "a_foo", "1.2.79"), } if "c" in env_value: expected |= { - VariantProperty("abi_dependency", "c", "7"), - VariantProperty("abi_dependency", "c", "7.8"), - VariantProperty("abi_dependency", "c", "7.8.9"), + VariantProperty("abi_dependency", "c_ns", "7"), + VariantProperty("abi_dependency", "c_ns", "7.8"), + VariantProperty("abi_dependency", "c_ns", "7.8.9"), } if "d" in env_value: expected |= { diff --git a/variantlib/resolver/lib.py b/variantlib/resolver/lib.py index 75932b2..c0dab4e 100644 --- a/variantlib/resolver/lib.py +++ b/variantlib/resolver/lib.py @@ -128,7 +128,10 @@ def inject_abi_dependency( """Inject supported vairants for the abi_dependency namespace""" # 1. Automatically populate from the current python environment - packages = {dist.name: dist.version for dist in importlib.metadata.distributions()} + packages = { + _normalize_package_name(dist.name): dist.version + for dist in importlib.metadata.distributions() + } # 2. Manually fed from environment variable # Env Var Format: `VARIANT_ABI_DEPENDENCY=packageA==1.2.3,...,packageZ==7.8.9` @@ -145,6 +148,7 @@ def inject_abi_dependency( ) continue + pkg_name = _normalize_package_name(pkg_name) if (old_version := packages.get(pkg_name)) is not None: logger.warning( "`VARIANT_ABI_DEPENDENCY` overrides package version: " @@ -162,7 +166,7 @@ def inject_abi_dependency( supported_vprops.extend( VariantProperty( namespace=VARIANT_ABI_DEPENDENCY_NAMESPACE, - feature=_normalize_package_name(pkg_name), + feature=pkg_name, value=_ver, ) for _ver in _generate_version_matches(pkg_version)