diff --git a/peps/pep-9999.rst b/peps/pep-9999.rst new file mode 100644 index 00000000000..09e12b4122b --- /dev/null +++ b/peps/pep-9999.rst @@ -0,0 +1,838 @@ +PEP: 9999 +Title: Wheel Variants: Providers +Author: Michał Górny , + Jonathan Dekhtiar , + Konstantin Schütze , + Ralf Gommers , + Hyunsu Cho , + Andrey Talman , + Michael Sarahan , + Donald Stufft , + Andy R. Terrel +Discussions-To: Pending +Status: Draft +Type: Standards Track +Topic: Packaging +Requires: 825 +Created: 22-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. + +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 +---------------- + +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} + +- feature-order : list[str] = [] + +- 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 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 +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. + +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 +'''''''''''' + +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 [] + + configs = [ + VariantFeatureConfig( + name="min_version", + # [current, current - 1, ..., 1] + values=[str(x) for x in range(current_version, 0, -1)], + multi_value=False, + ), + ] + + # 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 +''''''''''''''''' + +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`` 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 +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 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. + +==================================== ================== +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 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. + + +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. + + +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, Barry Warsaw, Bradley Dice, Charlie Marsh, Chris Gottbrath, +Dmitry Rogozhkin, Eli Uriegas, Emma Smith, Geoffrey Thomas, Henry Schreiner, +Jeff Daily, Jeremy Tanner, Jithun Nair, Keith Kraus, Leo Fang, Mike McCarty, +Nikita Shulga, Paul Ganssle, Robert Maynard, Vyas Ramasubramani, and Zanie Blue. + + +Change History +============== + +- 22-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. + - 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 +========== + +- :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..15b939f9867 --- /dev/null +++ b/peps/pep-9999/variant-schema-0.2.0.json @@ -0,0 +1,151 @@ +{ + "$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": { + "anyOf": [ + { "required": ["feature-order"] }, + { "required": ["static-properties"] } + ] + } + }, + { + "required": ["static-properties"], + "not": { + "anyOf": [ + { "required": ["requires"] }, + { "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 +}