From 1e4b4420625dd1c15693b77ac6fe3e057d0c261f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 19 Feb 2026 20:06:34 +0100 Subject: [PATCH 01/14] PEP 9999: Wheel Variants: Providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the second PEP split off PEP 817. Its focus is on how variant properties are governed and how their compatibility is determined. This is done either via static feature and feature value compatibility lists specified in the variant metadata, or by querying opt-in plugins that are Python packages, but can also be vendored or reimplemented by the tools. A static list can also be provided in place of querying the plugins. Additionally, an ABI Dependency Variant Provider is defined to facilitate builds of the same package against different dependency versions. Compared to PEP 817, the provider metadata has been largely simplified by removing all the bits deemed not strictly necessary, and adjusted for the changes in PEP 825. The provider plugins have been made opt-in (with provisions for tools to make some of them opt-out). The recommendations for governance of opt-out providers and building variant wheels will follow in subsequent PEPs. Signed-off-by: Michał Górny --- peps/pep-9999.rst | 833 ++++++++++++++++++ .../pep-9999/appendix-variant-json-schema.rst | 11 + peps/pep-9999/variant-schema-0.2.0.json | 141 +++ 3 files changed, 985 insertions(+) create mode 100644 peps/pep-9999.rst create mode 100644 peps/pep-9999/appendix-variant-json-schema.rst create mode 100644 peps/pep-9999/variant-schema-0.2.0.json diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst new file mode 100644 index 00000000000..5e122ceb59a --- /dev/null +++ b/peps/pep-9999.rst @@ -0,0 +1,833 @@ +PEP: 9999 +Title: Wheel Variants: Providers +Author: Jonathan Dekhtiar , + Michał Górny , + Konstantin Schütze , + Ralf Gommers , + Andrey Talman , + Charlie Marsh , + Michael Sarahan , + Eli Uriegas , + Barry Warsaw , + Donald Stufft , + Andy R. Terrel +Discussions-To: Pending +Status: Draft +Type: Standards Track +Topic: Packaging +Requires: 825 +Created: 21-Sep-2026 +Post-History: Pending + +Abstract +======== + +This PEP extends :pep:`825` by defining how variant properties are +governed and how their compatibility is determined. This is done either +via static feature and feature value compatibility lists specified in +the variant metadata, or by querying opt-in plugins that are Python +packages, but can also be vendored or reimplemented by the tools. A +static list can also be provided in place of querying the plugins. +Additionally, an ABI Dependency Variant Provider is defined to +facilitate builds of the same package against different dependency +versions. + + +Motivation +========== + +:pep:`825` defines a protocol for recording additional compatibility +data for binary packages, in the form of variant properties, but does +not define how to obtain the matching information for the system. This +PEP defines how to determine which variant properties are compatible +with a particular system and how variant namespaces are governed. + +This PEP introduces three different ways of defining and obtaining +variant properties for three different use cases: + +1. Variants that express platform compatibility, for example GPU or CPU + capabilities. In this case, the goal is to select the best wheel that + is compatible with the particular system. + +2. Variants that express non-platform properties, such as different + BLAS/LAPACK or OpenMP implementations, or debug builds. Here all + variants that were built are compatible, and the goal is to provide + users with the ability to explicitly select a non-default variant. + +3. Variants that express compatibility with different dependency + versions, particularly aiming to express Application Binary Interface + (ABI) compatibility between extension modules. The goal is to enable + matching variants against other packages, where one source + distribution can be narrowed to specific versions of a dependency + during build. + + +Specification +============= + +Definitions +----------- + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this +document are to be interpreted as described in :rfc:`2119`. + + +Providers +--------- + +Variant properties, as defined in :pep:`825`, are organized into variant +namespaces. Every variant namespace used in a variant wheel MUST be +governed by a *variant provider*. Variant providers supply ordered lists +of compatible features and feature values corresponding to their +namespaces, as required by :pep:`825`. + +The namespace ``abi_dependency`` is reserved for the `ABI Dependency +Variant Provider <#abi-dependency-variant-provider-optional>`_. The +providers for all the other namespaces used MUST be defined in the +`provider information`_ dictionary in `variant metadata`_. This PEP is +concerned only with consuming said dictionary; the process of building +variant wheels will be covered in a subsequent PEP. + +Each of these providers either defines a static list of compatible +features and their values, or specifies a list of requirements referring +to Python packages. In the latter case, an API endpoint is either +explicitly specified or inferred from the first dependency string. The +canonical way of querying the list of compatible features and their +values is then to install the specified packages and call this endpoint. +The combination of dependency strings and the API endpoint are used to +uniquely identify the provider, while the respective version constraints +can be used to indicate the minimum versions emitting variant properties +used in the package. + +For security reasons, none of these packages are installed or used by +default. Tools can provide a way to securely use a subset of provider +packages, for example through vendoring, a reimplementation or an +embedded lockfile. To aid in this, a shared list of such packages along +with trusted implementations will be maintained; the details of said +list and its governance will be provided in a subsequent PEP. Using +provider plugins that are not trusted, such as the actual package from +an index fulfilling the requirement, requires explicit user opt-in. This +ensures only trusted code is executed during installation, while still +supporting the evolution of providers and establishing clear governance +over each namespace. + +Installers and other tools that need to determine whether a variant is +compatible with the system MUST follow the specified algorithm in order +to obtain the list of compatible features and feature values for every +namespace: + +1. The tool SHOULD implement a way for the user to provide a static list + of compatible features and their values for a particular provider. + Standardizing this format is left to a future PEP. If the user + provided said list, the tool MUST use it exclusively. Otherwise, + proceed to step 2. + +2. The tool MUST read the ``optional`` key from the `provider + information`_ to determine whether the variant provider is disabled + by default. It SHOULD provide a way to explicitly enable a provider, + and MAY provide a way to disable one. If a provider is disabled, the + list of compatible features is empty. Otherwise, proceed to step 3. + +3. The tool MUST read the ``static-properties`` key from the `provider + information`_ dictionary. If it is present, the tool MUST use the + static lists of properties therein exclusively. Otherwise, proceed to + step 4. + +4. If no static list of compatible features and their values is + provided, the tool MUST read the ``requires`` and ``plugin-api`` keys + in the `provider information`_ dictionary to determine the packages + providing the compatibility information. If the tool provides a + secure implementation of the specific providers, it SHOULD obtain + the list from their implementation. Otherwise, proceed to step 5. + +5. The tool SHOULD provide a way for the user to permit installing + specific provider packages. It MUST NOT install or use any provider + packages that are not considered trusted. It SHOULD use an isolated + virtual environment while installing provider plugin packages. + +6. The tool MUST query the trusted and installed provider packages via + the `provider plugin API`_ to obtain the list of compatible features + and feature values. For providers that were not trusted, it MUST + assume that the list of compatible features is empty. + + +Variant metadata +---------------- + +This PEP extends the metadata introduced in :pep:`825` with an +additional ``providers`` key, described in the `Provider information`_ +subsection. + +.. code:: text + + (root) + | + +- $schema + +- default-priorities + +- variants + +- providers + +- {namespace} + +- optional : bool = False + +- plugin-api : str | None = None + +- requires : list[str] = [] + +- static-properties + +- {feature} : list[str] = [] + +This structure corresponds to the version ``0.2.0`` of the format. An +update of the proposed JSON schema for the current format version is +included in the Appendix of this PEP. The schema is available in +:ref:`pep9999-variant-json-schema`. + + +Provider information +'''''''''''''''''''' + +Variant metadata in a variant wheel must include a ``providers`` key. It +corresponds to a dictionary, in which the keys are namespaces, the +values are dictionaries with provider information. It specifies how to +install and use variant providers. All the namespaces found in +``default-priorities.namespace`` MUST be listed in this dictionary, and +other keys MUST NOT be present. + +The use of provider information is described in the `Providers`_ and +`Provider plugin API`_ sections. + +Exactly one of the following keys MUST be present in the provider +information dictionary: + +- ``static-properties: dict[str, list[str]]``: The dictionary of static + variant compatibility data. The keys correspond to feature names, + while the values are the ordered list of compatible feature values. + Since the dictionaries in JSON are unsorted, if more than one key is + specified, then the order for all features MUST be specified in the + ``feature-order`` key. + +- ``requires: list[str]``: A list of one or more package + :ref:`dependency specifiers `, that are used to + install the provider plugin. If the dependency specifiers include + environment markers, these are evaluated against the environment where + the plugin is being installed, and the requirements for which the + markers evaluate to false are filtered out. At least one dependency + MUST remain present in every possible environment after filtering. + Additionally, if ``plugin-api`` is not specified explicitly, the first + dependency that remains after filtering MUST always reference the same + package name as the first dependency prior to filtering. + +A provider information dictionary MAY additionally contain the following +key: + +- ``optional: bool``: Whether the provider is optional. Defaults to + ``false``. If it is ``true``, the provider is disabled by default + and needs to be enabled explicitly. + +If the ``requires`` key is present, the dictionary MAY additionally +contain the following key: + +- ``plugin-api: str``: The API endpoint for the plugin. If it is + specified, it MUST be an object reference as explained in the `API + endpoint`_ section. If it is not specified, a default value is + determined by :ref:`normalizing the package name + ` from the first dependency specifier in + ``requires`` and then replacing all ``-`` characters with ``_`` in the + normalized package name. + +It is invalid to specify ``requires`` or ``plugin-api`` if +``static-properties`` are present. + +If the ``static-properties`` key is present, the dictionary MAY +additionally contain the following key: + +- ``feature-order: list[str]``: The list controlling the order of + compatible features. When present, all keys found in + ``static-properties`` MUST be listed here. Entries not found in the + ``static-properties`` dictionary MUST NOT appear in this list. + +The ``providers`` dictionary is copied into index-level metadata. For +the metadata to be consistent, the same keys MUST always correspond to +the same values. When combining metadata, the resulting ``providers`` +dictionary MUST be a union of all the input dictionaries. + + +Example +''''''' + +Both the variant metadata example and the index-level metadata example +from :pep:`825` can be extended in the following way: + +.. code:: json5 + + { + // The schema URL will be replaced with the final URL on packaging.python.org + "$schema": "https://variants-schema.wheelnext.dev/peps/9999/v0.2.0.json", + + "default-priorities": { + // MUST list all namespaces used. + "namespace": ["x86_64", "aarch64", "blas_lapack"], + }, + + "providers": { + // MUST list all namespaces used. + "aarch64": { + // Specifies provider plugin package. REQUIRED since there is no + // "static-properties" -- plugin will be queried at install time. + "requires": ["provider-variant-aarch64 >=0.0.1"] + // "plugin-api" is OPTIONAL here. It is inferred from "requires": + // "plugin-api": "provider_variant_aarch64" + }, + "blas_lapack": { + // Specifies compatible properties. No install-time querying is + // necessary. + "static-properties": { + // Values are ordered -- by default, openblas variant is preferred + // over mkl, and accelerate over the other two. + "library": ["accelerate", "openblas", "mkl"] + }, + }, + "x86_64": { + // Specifies provider plugin package. REQUIRED since there is no + // "static-properties" -- plugin will be queried at install time. + "requires": [ + "provider-variant-x86-64 >=0.0.1; python_version >= '3.12'", + "legacy-provider-variant-x86-64 >=0.0.1; python_version < '3.12'" + ], + // Overrides plugin API endpoint. REQUIRED since "requires" may + // evaluate to two different packages depending on Python version. + "plugin-api": "provider_variant_x86_64.plugin:X8664Plugin" + } + }, + + "variants": { + // same as in PEP 825 + } + } + + +Provider plugin API +------------------- + +High level design +''''''''''''''''' + +To make it easier to discover and install plugins, they SHOULD be +published in the same indexes as the packages using them. In particular, +packages published to PyPI MUST NOT rely on plugins that need to be +installed from other indexes. + +Except for namespaces reserved as part of this PEP and variant providers +using the ``static-properties`` key, installable Python packages MUST be +provided for plugins. The entire dependency tree of such packages MUST +be installable using non-variant wheels, and variant wheels MUST NOT be +used while installing them. + +As noted in the `Providers`_ section, these plugins can also be +reimplemented by tools needing them. If that is the case, the API +defined in this section does not apply to these implementations. +However, the reimplementer is responsible for ensuring consistent +behavior with the published provider. The reimplemenetations and +vendored providers MUST be matched to the published providers using the +``requires`` and ``plugin-api`` keys, rather than namespaces. + +A plugin implemented as Python package exposes callables that are called +via: + +.. code:: text + + {API endpoint}.{callable name}({arguments}...) + +These can be implemented either as module-level functions, class methods +or static methods. The specifics are provided in the subsequent +sections. + + +API endpoint +'''''''''''' + +The location of the plugin code is called an "API endpoint", and it is +expressed using the object reference notation following the +:doc:`packaging:specifications/entry-points`: + +.. code:: text + + {import_path}(:{object_path})? + +An API endpoint specification is equivalent to the following Python +pseudocode: + +.. code:: python + + import {import_path} + + if "{object_path}": + plugin = {import_path}.{object_path} + else: + plugin = {import_path} + +API endpoints are used in two contexts: + +a. in the ``plugin-api`` key of `provider information`_ in variant + metadata, either explicitly or inferred from the package name in the + ``requires`` key. This is the primary method of using the plugin when + building and installing wheels. + +b. as the value of an installed entry point, as described in `variant + plugin discovery`_. + + +Variant plugin discovery +'''''''''''''''''''''''' + +The Python packages providing variant provider plugins SHOULD install an +entry point to facilitate discovery by variant-related utilities. The +entry point MUST be placed in the ``variant_plugins`` group, its name +being the recommended namespace name, and its value being the plugin's +`API endpoint`_. For example, a plugin can declare the following entry +point: + +.. code:: toml + + [project.entry-points.variant_plugins] + x86_64 = "provider_variant_x86_64" + +A tool that facilitates such discovery mechanism MUST collect all entry +points in the ``variant_plugins`` group and use their respective values +to determine the API endpoints to call. They MAY use the entry point +name to recommend the namespace to use, and the associated package +distribution metadata to determine which package provides a given +plugin. + +For example, with ``provider-variant-x86-64==0.0.3`` package installing +the entry point shown above, ``variantlib update-pyproject-toml --add +x86_64`` would add the following metadata to ``pyproject.toml``: + +.. code:: toml + + [variant.default-priorities] + namespace = ["x86_64"] + + [variant.providers.x86_64] + requires = ["provider-variant-x86-64 >=0.0.3"] + + +Variant feature config class +'''''''''''''''''''''''''''' + +The variant feature config class is used as a return value in plugin API +functions. It defines a single variant feature, along with a list of +possible values. Depending on the context, the order of values MAY be +significant. Its instance MUST provide the following properties or +attributes: + +- ``name: str`` specifying the feature name. + +- ``multi_value: bool`` specifying whether the feature is allowed to + have multiple corresponding values within a single entry in the + ``variants`` dictionary. If it is ``False``, then it is an error to + specify multiple values for the feature. + +- ``values: list[str]`` specifying feature values. In contexts where the + order is significant, the values MUST be ordered from the most + preferred to the least preferred. This list MUST NOT be empty. + +Equivalently, it can be described using the following protocol: + +.. code:: python + + from abc import abstractmethod + from typing import Protocol + + + class VariantFeatureConfigType(Protocol): + @property + @abstractmethod + def name(self) -> str: + """Feature name""" + raise NotImplementedError + + @property + @abstractmethod + def multi_value(self) -> bool: + """Does this property allow multiple values per variant?""" + raise NotImplementedError + + @property + @abstractmethod + def values(self) -> list[str]: + """List of values, possibly ordered from most preferred to least""" + raise NotImplementedError + + +Plugin interface +'''''''''''''''' + +The plugin interface, as referenced by the `API endpoint`_, MUST provide +the following function: + +- ``get_supported_configs() -> list[VariantFeatureConfigType]`` that + returns a list of feature names and their values that are compatible + with the system the plugin is running on. The variant feature and + value lists MUST be ordered from the most preferred to the least + preferred. + +Equivalently, it can be described using the following protocol: + +.. code:: python + + from abc import abstractmethod + from typing import Protocol + + + class PluginType(Protocol): + @classmethod + @abstractmethod + def get_supported_configs(cls) -> list[VariantFeatureConfigType]: + """Get ordered lists of compatible features and their values""" + raise NotImplementedError + + +Example implementation +'''''''''''''''''''''' + +.. code:: python + + from dataclasses import dataclass + + + @dataclass + class VariantFeatureConfig: + name: str + values: list[str] + multi_value: bool + + + # internal -- provided for illustrative purpose + _ALL_GPUS = ["narf", "poit", "zort"] + + + def _get_current_version() -> int: + """Returns currently installed runtime version""" + ... # implementation not provided + + + def _is_gpu_available(codename: str) -> bool: + """Is specified GPU installed?""" + ... # implementation not provided + + + def get_supported_configs() -> list[VariantFeatureConfig]: + current_version = _get_current_version() + if current_version is None: + # no runtime found, system not supported at all + return [] + + return [ + VariantFeatureConfig( + name="min_version", + # [current, current - 1, ..., 1] + values=[str(x) for x in range(current_version, 0, -1)], + multi_value=False, + ), + VariantFeatureConfig( + name="gpu", + # this may be empty if no GPUs are supported -- + # 'example :: gpu feature' is not supported then; + # but wheels with no GPU-specific code and only + # 'example :: min_version' could still be installed + values=[x for x in _ALL_GPUS if _is_gpu_available(x)], + multi_value=True, + ), + ] + + +Future extensions +''''''''''''''''' + +The future versions of this specification, as well as third-party +extensions MAY introduce additional attributes on the plugin instances. +The implementations SHOULD ignore them. + +For best compatibility, all private attributes SHOULD be prefixed with +an underscore (``_``) character to avoid incidental conflicts with +future extensions. + + +ABI Dependency Variant Provider (Optional) +------------------------------------------- + +This section describes an OPTIONAL extension to the wheel variant +specification. Tools that choose to implement this feature MUST follow +this specification. Tools that do not implement this feature MUST treat +the variants using it as incompatible, and SHOULD inform users when such +wheels are skipped. + +The variant namespace ``abi_dependency`` is reserved for expressing that +different builds of the same version of a package are compatible with +different versions or version ranges of a dependency. This namespace +MUST NOT be listed in the `provider information`_ dictionary nor in the +``default-priorities.namespace`` dictionary, and can only appear in the +``variants`` dictionary. It is not taken into consideration in variant +ordering. Package maintainers SHOULD NOT be publishing variant wheels +where two different ``abi_dependency`` property sets could be compatible +with the same system. + +Within this namespace, zero or more properties can be used to express +compatible dependency versions. For each property, the feature name MUST +be the :ref:`normalized name ` of the +dependency, whereas the value MUST be a valid release segment of a +public version identifier, as defined by the +:doc:`packaging:specifications/version-specifiers` specification. It +MUST contain up to three version components, that are matched against +the installed version same as the ``=={value}.*`` specifier. Notably, +trailing zeroes match versions with fewer components (e.g. ``2.0`` +matches release ``2`` but not ``2.1``). This also implies that the +property values have different semantics than PEP 440 versions, in +particular ``2``, ``2.0`` and ``2.0.0`` represent different ranges. + +Versions with nonzero epoch are not supported. + +==================================== ================== +Variant Property Matching Rule +==================================== ================== +``abi_dependency :: torch :: 2`` ``torch==2.*`` +``abi_dependency :: torch :: 2.9`` ``torch==2.9.*`` +``abi_dependency :: torch :: 2.8.0`` ``torch==2.8.0.*`` +==================================== ================== + +Multiple variant properties with the same feature name can be used to +indicate wheels compatible with multiple providing package versions, +e.g.: + +.. code:: text + + abi_dependency :: torch :: 2.8.0 + abi_dependency :: torch :: 2.9.0 + +This means the wheel is compatible with both PyTorch 2.8.0 and 2.9.0. + + +Rationale +========= + +The primary use case for providers is determining platform +compatibility, which implies that they need to be used at install time. +The specification proposes a plugin mechanism using Python packages, +with the interface inspired by :pep:`517`. Such a mechanism has a few +advantages: + +- The individual plugins can be governed independently, by the + stakeholders having necessary knowledge and hardware. Additional + compatibility axes (new CPUs, GPUs) do not impose direct maintenance + costs on tools interacting with variant wheels, nor on + centrally-maintained libraries such as ``packaging``. + +- The plugins can be updated as frequently as necessary, without being + tied to tool release schedules. + +- The plugins provide a unified interface for testing new providers. New + plugins can be developed and tested locally without having to patch + multiple tools, and released to the public after proving the concept. + +At the same time, it is understood that installing additional Python +packages and running the code from them introduces additional attack +vector (as discussed in `security implications`_). For this reason, +plugin packages are entirely opt-in, and a few mechanisms are provided +to improve the user experience without compromising security: + +- Users can provide static compatibility lists to avoid querying the + providers. This also permits deploying packages for different systems + than the one running the installer. + +- Tools can maintain their own lists of trusted provider plugins that + are enabled by default, or they can vendor or reimplement some + providers. This is entirely voluntary, to avoid imposing an additional + maintenance burden on tool maintainers. At the same time, the ability + to reimplement providers avoids introducing a performance bottleneck + on tools that aren't written in Python. + +- Variant wheels can include static lists of compatible properties, to + facilitate variants that do not need querying platform capabilities, + such as builds done against different BLAS/LAPACK libraries. + +Furthermore, individual providers can be disabled by default (made +optional), to introduce variants that can only be selected explicitly, +for example debug or experimental builds of packages. + +Installing provider plugins in isolated environments is recommended, as +that permits tools to automatically deploy them without affecting the +system packages. However, this is not a requirement. For example, build +frontends may prefer reusing the isolated build environment for this. + +Providers are defined as part of the variant metadata. From the metadata +consistency requirements, it follows that multiple variant wheels for a +single package version published on a single index must always use the +same provider for a given namespace. This requirement does not hold +across different packages, package versions or indexes. This permits +both adjusting dependency bounds and switching to different provider +plugins with a new package version. + +The ``requires`` and ``plugin-api`` keys follow the precedent of +``build-system.requires`` and ``build-system.build-backend`` keys of +:pep:`517`. However, the ``plugin-api`` key has been made optional and +defaults to being inferred from the package name, reducing boilerplate. + +It is recommended for plugins to install entry points to facilitate +discovery when installed in development environments. This can be used +by variant-related tools, for example to aid debugging variant selection +or help configuring source trees. + +The `ABI Dependency Variant Provider +<#abi-dependency-variant-provider-optional>`_ is defined separately, as +it needs to interact with the dependency resolver. To avoid adding +significant complexity to the plugin API and at the same time +restricting the actual implementation, it has been made a special case. +It is entirely optional to avoid adding maintenance burden to tool +maintainers. + + +Backwards Compatibility +======================= + +This PEP does not introduce any new backwards compatibility +considerations, compared to :pep:`825`. + + +Security Implications +===================== + +This PEP introduces a plugin system for querying the platform +capabilities. Tools may install these packages and execute the code +within them during dependency resolution or wheel processing. This +elevates the supply-chain attack potential by introducing two new points +for malicious actors to inject arbitrary code payload: + +1. Publishing a version of a variant provider plugin or one of its + dependencies with malicious code. +2. Introducing a malicious variant provider plugin in an existing + package metadata. + +While such attacks can already be done to the package's dependencies, it +is prudent not to add another attack surface. + +A similar issue already exists in the packaging ecosystem when packages +are installed from source distributions, with build backends and other +build dependencies are being installed and executed by default. Users +are often unaware of this risk. Securing build dependencies is +non-trivial and not commonly done. More commonly, users opt-out of +source distribution builds altogether instead. + +Unfortunately, an opt-in system creates a risk of security fatigue. +Users wishing to use variant wheels may start blanket-enabling all use +of provider plugins, reintroducing the RCE danger. To avoid this and +improve user experience, the PEP proposes that tool provide a selected +set of trusted plugins, only requiring opt-in for uncommon plugins. A +subsequent PEP will give further recommendations for improved user +experience. + + +How to Teach This +================= + +This PEP is focused on installing variant wheels. The primary source of +information for the users will be the user interface of installers, +supplemented by their documentation and installation instructions of +specific packages publishing variant wheels. The documentation present +on ``packaging.python.org`` will need to be updated as well. + +Ideally, in the most common use cases variants will work out of the box +and non-expert users will not need to be aware of them, much like they +do not need to be aware of Platform compatibility tags. Some packages +will be able to simplify their installation instructions to a single +command covering the different hardware accelerators. + +Expert users will need guidance that will largely be specific to +particular installer implementation, as it involves user interface +decisions. The following topics may need to be covered, depending on the +features implemented by the installer: + +- how to enable installing untrusted variant provider packages, and what + are the security implications of that +- how to enable optional providers +- how to generate and provide static compatibility data, enabling + deployment for remote targets +- how to explicitly select a specific variant +- how to alter variant selection, for example by specifying preferred + properties or filtering out undesirable properties + +The topic of teaching package maintainers will be addressed in a +subsequent PEP, along with building variant wheels. + + +Reference Implementation +======================== + +The `variantlib `__ project +contains a reference implementation of this PEP. + + +Rejected Ideas +============== + +An approach without provider plugins +------------------------------------ + +Rather than introducing provider plugins, the rules governing every +variant namespace could be defined via PEPs. However, such an approach +would be less scalable and impose additional effort on stakeholders, PEP +editors and tool maintainers. + +Every new namespace would have to go through standardization process, +followed by explicit implementation process. Deployment of new variant +properties would be entirely dependent on tool updates. The added +maintenance cost could lead to support for less popular variant axes not +being accepted, or lack of feature parity between different tools. + + +Acknowledgements +================ + +This work would not have been possible without the contributions and +feedback of many people in the Python packaging community. In +particular, we would like to credit the following individuals for their +help in shaping this PEP (in alphabetical order): + +Alban Desmaison, Bradley Dice, Chris Gottbrath, Dmitry Rogozhkin, +Emma Smith, Geoffrey Thomas, Henry Schreiner, Jeff Daily, Jeremy Tanner, +Jithun Nair, Keith Kraus, Leo Fang, Mike McCarty, Nikita Shulga, +Paul Ganssle, Philip Hyunsu Cho, Robert Maynard, Vyas Ramasubramani, +and Zanie Blue. + + +Change History +============== + +- 21-Sep-2026 + + - Initial version, split from :pep:`817` draft. + - Namespaces have been removed from the `provider plugin API`_. + Instead, the namespace is named by the package in `variant + metadata`_. + - The ``enable-if`` and ``install-time`` keys have been removed from + `provider information`_, as they were deemed redundant. + - The ``static-properties`` table has been moved into `provider + information`_, and made mutually exclusive with the ``requires`` + key. + - The ``feature-order`` key has been added to facilitate the static + property ordering after the removal of + ``default-priorities.feature`` from :pep:`825`. + - The ``values`` member of the `variant feature config class`_ is now + required to be non-empty. + - The entry point name is now used to provide the recommended + namespace for a provider. + + +Appendices +========== + +- :ref:`pep9999-variant-json-schema` + + +Copyright +========= + +This document is placed in the public domain or under the +CC0-1.0-Universal license, whichever is more permissive. diff --git a/peps/pep-9999/appendix-variant-json-schema.rst b/peps/pep-9999/appendix-variant-json-schema.rst new file mode 100644 index 00000000000..b5e05c1f4b2 --- /dev/null +++ b/peps/pep-9999/appendix-variant-json-schema.rst @@ -0,0 +1,11 @@ +:orphan: + +.. _pep9999-variant-json-schema: + +Appendix: JSON Schema for Variant Metadata +========================================== + +.. literalinclude:: variant-schema-0.2.0.json + :language: json + :linenos: + :name: variant-json-schema diff --git a/peps/pep-9999/variant-schema-0.2.0.json b/peps/pep-9999/variant-schema-0.2.0.json new file mode 100644 index 00000000000..9afe148a193 --- /dev/null +++ b/peps/pep-9999/variant-schema-0.2.0.json @@ -0,0 +1,141 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://variants-schema.wheelnext.dev/peps/9999/v0.2.0.json", + "title": "Variant metadata, v0.2.0", + "description": "The format for variant metadata (variant.json) and index-level metadata ({name}-{version}-variants.json)", + "type": "object", + "properties": { + "$schema": { + "description": "JSON schema URL", + "type": "string" + }, + "default-priorities": { + "description": "Default priorities for ordering variants", + "type": "object", + "properties": { + "namespace": { + "description": "Namespaces (in order of preference)", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false, + "required": [ + "namespace" + ] + }, + "providers": { + "description": "Mapping of namespaces to provider information", + "type": "object", + "patternProperties": { + "^[A-Za-z0-9_]+$": { + "type": "object", + "description": "Provider information", + "properties": { + "feature-order": { + "description": "Feature order for static-properties", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 1, + "uniqueItems": true + }, + "optional": { + "description": "Whether the provider is optional (disabled by default)", + "type": "boolean" + }, + "plugin-api": { + "description": "Object reference to plugin class", + "type": "string", + "pattern": "^([a-zA-Z0-9._]+ *: *[a-zA-Z0-9._]+)|([a-zA-Z0-9._]+)$" + }, + "requires": { + "description": "Dependency specifiers for how to install the plugin", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "uniqueItems": true + }, + "static-properties": { + "description": "Static properties (by feature name)", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Ordered value list", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 0, + "uniqueItems": true + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "oneOf": [ + { + "required": ["requires"], + "not": {"required": ["feature-order"]} + }, + { + "required": ["static-properties"], + "not": {"required": ["plugin-api"]} + } + ] + } + }, + "additionalProperties": false + }, + "variants": { + "description": "Mapping of variant labels to properties", + "type": "object", + "patternProperties": { + "^[a-z0-9_.]+$": { + "type": "object", + "description": "Mapping of namespaces in a variant", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "Mapping of feature names in a namespace", + "type": "object", + "patternProperties": { + "^[a-z0-9_]+$": { + "description": "List of values for this variant feature", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_.]+$" + }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "required": [ + "$schema", + "default-priorities", + "providers", + "variants" + ], + "additionalProperties": false +} From 512002b36c3603743fdcd6c1db68bda909924b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 13:07:35 +0200 Subject: [PATCH 02/14] Add `feature-order` to the diagram MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 5e122ceb59a..f5ca0a0e137 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -168,6 +168,7 @@ subsection. +- variants +- providers +- {namespace} + +- feature-order : list[str] = [] +- optional : bool = False +- plugin-api : str | None = None +- requires : list[str] = [] From 4179654c7d84915c1d71fb1857e7ad6beaaede19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 13:09:25 +0200 Subject: [PATCH 03/14] Fix typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index f5ca0a0e137..6047d811b10 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -325,8 +325,8 @@ As noted in the `Providers`_ section, these plugins can also be reimplemented by tools needing them. If that is the case, the API defined in this section does not apply to these implementations. However, the reimplementer is responsible for ensuring consistent -behavior with the published provider. The reimplemenetations and -vendored providers MUST be matched to the published providers using the +behavior with the published provider. The reimplementations and vendored +providers MUST be matched to the published providers using the ``requires`` and ``plugin-api`` keys, rather than namespaces. A plugin implemented as Python package exposes callables that are called @@ -719,7 +719,7 @@ source distribution builds altogether instead. Unfortunately, an opt-in system creates a risk of security fatigue. Users wishing to use variant wheels may start blanket-enabling all use of provider plugins, reintroducing the RCE danger. To avoid this and -improve user experience, the PEP proposes that tool provide a selected +improve user experience, the PEP proposes that tools provide a selected set of trusted plugins, only requiring opt-in for uncommon plugins. A subsequent PEP will give further recommendations for improved user experience. From c359673fe20e0b427f52f806882b1fac51205c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 13:10:26 +0200 Subject: [PATCH 04/14] Fix thinko: `default-priorities.namespace` is a list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 6047d811b10..671867b3368 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -565,7 +565,7 @@ The variant namespace ``abi_dependency`` is reserved for expressing that different builds of the same version of a package are compatible with different versions or version ranges of a dependency. This namespace MUST NOT be listed in the `provider information`_ dictionary nor in the -``default-priorities.namespace`` dictionary, and can only appear in the +``default-priorities.namespace`` list, and can only appear in the ``variants`` dictionary. It is not taken into consideration in variant ordering. Package maintainers SHOULD NOT be publishing variant wheels where two different ``abi_dependency`` property sets could be compatible From 5026ab7de57474e296a32ff5128311ea82c50927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 15:26:30 +0200 Subject: [PATCH 05/14] Use wheel normalization for feature names and versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As pointed out by @konstin, regular normalization produces hyphens which we don't support. Instead use wheel normalization, the same way we do in PEP 825. Signed-off-by: Michał Górny --- peps/pep-9999.rst | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 671867b3368..fc9fe9b765e 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -573,16 +573,20 @@ with the same system. Within this namespace, zero or more properties can be used to express compatible dependency versions. For each property, the feature name MUST -be the :ref:`normalized name ` of the -dependency, whereas the value MUST be a valid release segment of a -public version identifier, as defined by the -:doc:`packaging:specifications/version-specifiers` specification. It -MUST contain up to three version components, that are matched against -the installed version same as the ``=={value}.*`` specifier. Notably, -trailing zeroes match versions with fewer components (e.g. ``2.0`` -matches release ``2`` but not ``2.1``). This also implies that the -property values have different semantics than PEP 440 versions, in -particular ``2``, ``2.0`` and ``2.0.0`` represent different ranges. +be the name of the dependency and the value MUST be a valid release +segment of a public version identifier, as defined by the +:doc:`packaging:specifications/version-specifiers` specification. Both +the dependency name and version MUST be normalized according to the same +rules as wheel files, as found in the +:ref:`packaging:wheel-file-name-spec` of the Binary Distribution Format +specification. + +The feature version MUST contain up to three version components, that +are matched against the installed version same as the ``=={value}.*`` +specifier. Notably, trailing zeroes match versions with fewer components +(e.g. ``2.0`` matches release ``2`` but not ``2.1``). This also implies +that the property values have different semantics than PEP 440 versions, +in particular ``2``, ``2.0`` and ``2.0.0`` represent different ranges. Versions with nonzero epoch are not supported. From e770a5fadc5ce6bac4ecb21c7a1cca4dae7e7f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 15:34:31 +0200 Subject: [PATCH 06/14] Fix handling exclusive keys in the schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As pointed out by @konstin, `requires`, `static-properties` and `plugin-api` would be accepted by the previous variant. Signed-off-by: Michał Górny --- peps/pep-9999/variant-schema-0.2.0.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/peps/pep-9999/variant-schema-0.2.0.json b/peps/pep-9999/variant-schema-0.2.0.json index 9afe148a193..d1dfb1a7658 100644 --- a/peps/pep-9999/variant-schema-0.2.0.json +++ b/peps/pep-9999/variant-schema-0.2.0.json @@ -88,11 +88,21 @@ "oneOf": [ { "required": ["requires"], - "not": {"required": ["feature-order"]} + "not": { + "anyOf": [ + { "required": ["feature-order"] }, + { "required": ["static-properties"] } + ] + } }, { "required": ["static-properties"], - "not": {"required": ["plugin-api"]} + "not": { + "anyOf": [ + { "required": ["requires"] }, + { "required": ["plugin-api"] } + ] + } } ] } From 841c78ed158d85113a0d9733f53732130b226447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 15:40:04 +0200 Subject: [PATCH 07/14] Fix misplaced `additionalProperties` in `static-properties` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999/variant-schema-0.2.0.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peps/pep-9999/variant-schema-0.2.0.json b/peps/pep-9999/variant-schema-0.2.0.json index d1dfb1a7658..15b939f9867 100644 --- a/peps/pep-9999/variant-schema-0.2.0.json +++ b/peps/pep-9999/variant-schema-0.2.0.json @@ -79,9 +79,9 @@ }, "minItems": 0, "uniqueItems": true - }, - "additionalProperties": false - } + } + }, + "additionalProperties": false } }, "additionalProperties": false, From 983099ab8b3249c8f396d01c2628b43121225781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 15:43:53 +0200 Subject: [PATCH 08/14] Fix GPU example not to produce empty value lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index fc9fe9b765e..d146d06485b 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -521,24 +521,31 @@ Example implementation # no runtime found, system not supported at all return [] - return [ + configs = [ VariantFeatureConfig( name="min_version", # [current, current - 1, ..., 1] values=[str(x) for x in range(current_version, 0, -1)], multi_value=False, ), - VariantFeatureConfig( - name="gpu", - # this may be empty if no GPUs are supported -- - # 'example :: gpu feature' is not supported then; - # but wheels with no GPU-specific code and only - # 'example :: min_version' could still be installed - values=[x for x in _ALL_GPUS if _is_gpu_available(x)], - multi_value=True, - ), ] + # this may be empty if no GPUs are supported -- + # 'example :: gpu feature' is not supported then; + # but wheels with no GPU-specific code and only + # 'example :: min_version' could still be installed + supported_gpus = [x for x in _ALL_GPUS if _is_gpu_available(x)] + if supported_gpus: + configs.append( + VariantFeatureConfig( + name="gpu", + values=supported_gpus, + multi_value=True, + ) + ) + + return configs + Future extensions ''''''''''''''''' From 222ba5dc7563bd829744a7d08e87609b7ce49a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 15:45:42 +0200 Subject: [PATCH 09/14] Remove "rejected ideas", as it's covered by PEP 817 in detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index d146d06485b..ea5593be211 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -776,24 +776,6 @@ The `variantlib `__ project contains a reference implementation of this PEP. -Rejected Ideas -============== - -An approach without provider plugins ------------------------------------- - -Rather than introducing provider plugins, the rules governing every -variant namespace could be defined via PEPs. However, such an approach -would be less scalable and impose additional effort on stakeholders, PEP -editors and tool maintainers. - -Every new namespace would have to go through standardization process, -followed by explicit implementation process. Deployment of new variant -properties would be entirely dependent on tool updates. The added -maintenance cost could lead to support for less popular variant axes not -being accepted, or lack of feature parity between different tools. - - Acknowledgements ================ From 62aa82462a9c694d648b38d71807b8bb046e9a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 15:51:06 +0200 Subject: [PATCH 10/14] Update the change history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index ea5593be211..55f23907d67 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -16,7 +16,7 @@ Status: Draft Type: Standards Track Topic: Packaging Requires: 825 -Created: 21-Sep-2026 +Created: 22-Sep-2026 Post-History: Pending Abstract @@ -794,7 +794,7 @@ and Zanie Blue. Change History ============== -- 21-Sep-2026 +- 22-Sep-2026 - Initial version, split from :pep:`817` draft. - Namespaces have been removed from the `provider plugin API`_. @@ -812,6 +812,9 @@ Change History required to be non-empty. - The entry point name is now used to provide the recommended namespace for a provider. + - The feature names and values in the `ABI Dependency Variant Provider + (Optional)`_ are now normalized according to the wheel normalization + rules, to match the restrictions in :pep:`825`. Appendices From 32bba86cde4309d34ad3bca26bb4d1033bea7ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 15:52:15 +0200 Subject: [PATCH 11/14] Restore the `get_supported_configs()` caching provision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was present in PEP 817, but got lost while moving things around. Signed-off-by: Michał Górny --- peps/pep-9999.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 55f23907d67..a9c03aacc57 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -470,6 +470,9 @@ the following function: value lists MUST be ordered from the most preferred to the least preferred. +The value returned by ``get_supported_configs()`` MAY be cached +throughout multiple packages in a single install session. + Equivalently, it can be described using the following protocol: .. code:: python From f3990c5f64fd5995ac840f90e7a6cc7abe525585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 16:11:13 +0200 Subject: [PATCH 12/14] Add a provision for plugin error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index a9c03aacc57..9a193839c3d 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -340,6 +340,10 @@ These can be implemented either as module-level functions, class methods or static methods. The specifics are provided in the subsequent sections. +Any errors occurring while installing or executing the plugin API, +including the plugin code terminating the calling process, MUST result +in tools aborting the install process. + API endpoint '''''''''''' From 00debbbedd2e0ffdb346061f12c3c11e61d4076b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 19:19:49 +0200 Subject: [PATCH 13/14] Replace the caching provision with consistent value provision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index 9a193839c3d..a21cd69162d 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -151,6 +151,10 @@ namespace: and feature values. For providers that were not trusted, it MUST assume that the list of compatible features is empty. +For every unique provider, the tool MUST obtain the list of compatible +features and their values only once throughout the install session and +use it across all the packages being installed. + Variant metadata ---------------- @@ -474,9 +478,6 @@ the following function: value lists MUST be ordered from the most preferred to the least preferred. -The value returned by ``get_supported_configs()`` MAY be cached -throughout multiple packages in a single install session. - Equivalently, it can be described using the following protocol: .. code:: python From 9283c994ab15e243f9f29760d4ac64e4149ae66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 19:21:09 +0200 Subject: [PATCH 14/14] Add to change history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9999.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst index a21cd69162d..0d53add9b66 100644 --- a/peps/pep-9999.rst +++ b/peps/pep-9999.rst @@ -823,6 +823,9 @@ Change History - The feature names and values in the `ABI Dependency Variant Provider (Optional)`_ are now normalized according to the wheel normalization rules, to match the restrictions in :pep:`825`. + - A provision has been added that a consistent list of compatible + feature names and values from a single provider must be used + throughout the install session. Appendices