From 7f2e494b468d351a62f1d8f691d237b92c7aabcd Mon Sep 17 00:00:00 2001 From: Jonathan Dekhtiar Date: Sat, 11 Oct 2025 17:00:12 -0500 Subject: [PATCH 1/4] [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 2/4] 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 3/4] 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 4/4] 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 # # ======================================================================= #