PEP 9998 – Wheel Variants: Building
- Author:
- Jonathan Dekhtiar <jonathan at dekhtiar.com>, Michał Górny <mgorny at quansight.com>, Konstantin Schütze <konstin at mailbox.org>, Ralf Gommers <ralf.gommers at gmail.com>, Andrey Talman <atalman at meta.com>, Charlie Marsh <charlie at astral.sh>, Michael Sarahan <msarahan at gmail.com>, Eli Uriegas <eliuriegas at meta.com>, Barry Warsaw <barry at python.org>, Donald Stufft <donald at stufft.io>, Andy R. Terrel <andy.terrel at gmail.com>
- Discussions-To:
- Pending
- Status:
- Draft
- Type:
- Standards Track
- Topic:
- Packaging
- Requires:
- 825, 9999
- Created:
- 02-Mar-2026
- Post-History:
- Pending
Abstract
Motivation
PEP 825 and PEP 9999 introduced variant wheels while focusing on the file format and installation process. This PEP follows up on that by defining a consistent data format and behavior for building variant wheels.
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.
Building variant wheels
Tools MAY support building variant wheels. When building variant wheels, they MUST ensure that the produced wheels conform to PEP 825 and PEP 9999. In particular, tools MUST ensure that:
- variant metadata conforms to the schema
- variant label matches the only key in the
variantsdictionary - there are entries in
default-priorities.namespaceandprovidersdictionaries for all namespaces found in variant properties - when
providers.{namespace}.static-propertiesspecifies more than one feature name, there are entries for all feature names indefault-priorities.feature.{namespace}
Additionally, tools MAY verify that:
- all specified variant properties are valid according to the respective providers (using extensions to provider plugin API)
Build backend support
Build backends MAY support building variant wheels. However, PEP 517 and PEP 660 hooks MUST NOT build variant wheels, unless they are explicitly requested by the user.
For consistency, it is RECOMMENDED that build backends implement the following interface for building variant wheels:
- variant metadata seed is stored in
pyproject.tomlfile, as described in pyproject.toml integration - the available variants are described in
variant.variantstable in said file - the
config_settingsdictionary of wheel building hooks accepts avariant-labelkey that enables building the specified variant fromvariant.variantsdictionary - the
get_requires_for_build_wheel()hook of PEP 517,get_requires_for_build_editable()hook of PEP 660, and hooks of similar purpose include the necessary provider plugin packages for the variant being built, and the build backend uses the version installed in the build environment as a result
pyproject.toml integration
This specification extends the pyproject.toml file originally
defined in PEP 518. Said file MAY contain a top-level variant
table that contains variant metadata for the project, as defined by
PEP 9999 or a subsequent specification, with the extensions listed
in this PEP, converted into the respective TOML types. When building
variant wheels, tools SHOULD use the information from this file to seed
the variant metadata in the built variant wheels. More specifically:
- the
default-prioritiesdictionary is used to seeddefault-prioritiesin the wheels - the
providersdictionary is used to seedprovidersin the wheels and to query providers at build time, if necessary - the
variantsdictionary MAY be used to provide a mapping from variant labels to variant properties, to facilitate requesting a build of a specific variant (nullvariant does not need to be specified)
Tools MAY provide additional methods of providing the necessary variant metadata.
Extension to provider metadata
The provider information dictionaries used as values in the
providers dictionary MAY contain the following key:
build-requires: list[str]: A list of one or more dependency specifiers, same asrequiresbut used only to populatestatic-propertiesat build time, as explained in populating static properties from provider plugins.
If build-requires is specified, both requires and
static-properties MUST NOT be present. In the resulting variant
wheel, the build-requires and plugin-api keys MUST be removed,
and static-properites MUST be added instead.
Example
The pyproject.toml file corresponding to the package with variant
metadata equivalent to the one provided in the example in PEP 9999
could look like the following:
[variant]
"$schema" = "https://variants-schema.wheelnext.dev/peps/9999/v0.2.0.json"
[variant.default-priorities]
# REQUIRED: specifies that x86_64 CPU properties are more important than
# aarch64 CPU properties (both are mutually exclusive, so the exact order
# does not matter), and both are more important than specific BLAS/LAPACK
# library:
namespace = ["x86_64", "aarch64", "blas_lapack"]
# OPTIONAL: makes "library" the most important feature in "blas_lapack"
# namespace
feature.blas_lapack = ["library"]
# OPTIONAL: makes ["mkl", "openblas"] the most important values of
# "blas_lapack :: library" feature
property.blas_lapack.library = ["mkl", "openblas"]
# REQUIRED: variant providers
# ---------------------------
# (keys must matchdefault-priorities.namespace)
[variant.providers.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",
]
# Overrides plugin API endpoint. OPTIONAL; without it, the API
# endpoint "provider_variant_aarch64" would be used.
plugin-api = "provider_variant_aarch64.plugin:AArch64Plugin"
[variant.providers.blas_lapack]
# REQUIRED: used to populate "static-properties".
build-requires = ["blas-lapack-variant-provider"]
[variant.providers.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"
# RECOMMENDED: variants that can be built
# ---------------------------------------
# "x8664v3_openblas" label corresponds to:
# - blas_lapack :: library :: openblas
# - x86_64 :: level :: v3
[variant.variants.x8664_v3_openblas]
blas_lapack.library = ["openblas"]
x86_64.level = ["v3"]
# "x8664v4_mkl" label corresponds to:
# - blas_lapack :: library :: mkl
# - x86_64 :: level :: v4
[variant.variants.x8664_v4_mkl]
blas_lapack.library = ["mkl"]
x86_64.level = ["v4"]
Extensions to provider plugin API
Provider plugins MAY be used at build time, if the provider metadata
specifies either requires or build-requires key, per the
extension to provider metadata section.
The plugin interface specified in PEP 9999 is extended by adding the following REQUIRED function:
get_all_config() -> list[VariantFeatureConfigType]that returns a list of variant feature names and their values that are valid according to the provider plugin. The ordering of the lists is insignificant. A particular plugin version MUST always return the same lists (modulo ordering), irrespective of any runtime conditions.
The value returned by get_supported_configs() MUST be a subset of
the feature names and values returned by get_all_configs() (modulo
ordering).
Additionally, the following OPTIONAL attribute is added:
is_aot_plugin: bool = False; if it set toTrue, then all valid feature names and values are always supported. In that case, theget_supported_configs()function MUST always return the same value asget_all_configs()(modulo ordering).
Example implementation
The example plugin implementation from PEP 9999 can be extended in the following way:
from dataclasses import dataclass
@dataclass
class VariantFeatureConfig:
name: str
values: list[str]
multi_value: bool
# internal -- provided for illustrative purpose
_MAX_VERSION = 4
_ALL_GPUS = ["narf", "poit", "zort"]
class MyPlugin:
@staticmethod
def get_supported_configs() -> list[VariantFeatureConfig]:
... # same as in PEP 9999
@staticmethod
def get_all_configs() -> list[VariantFeatureConfig]:
return [
VariantFeatureConfig(
name="min_version",
# [1, 2, 3, 4] (the order does not matter)
values=[str(x) for x in range(1, _MAX_VERSION + 1)],
# single-valued, since there is always one minimum
multi_value=False,
),
VariantFeatureConfig(
name="gpu",
# [narf, poit, zort]
values=_ALL_GPUS,
# multi-valued, since a package can target multiple GPUs
multi_value=True,
),
]
Example AoT plugin implementation
from dataclasses import dataclass
@dataclass
class _VariantFeatureConfig:
name: str
values: list[str]
multi_value: bool
is_aot_plugin = True
def get_supported_configs() -> list[_VariantFeatureConfig]:
return [
VariantFeatureConfig(
name="library",
values=["accelerate", "openblas", "mkl"],
multi_value=False,
),
]
def get_all_configs() -> list[_VariantFeatureConfig]:
return get_supported_configs()
Populating static properties from provider plugins
If a variant provider metadata used at build time contains the
build-requires key (as specified in the extension to provider
metadata section), the specified provider plugin is used to populate
the static-properties dictionary while building the variant wheel.
The packages to install and the plugin interface to use are determined as specified in PEP 9999, except that:
- the
build-requireskey is used in place ofrequireskey - all providers are assumed to be enabled and trusted
In particular, the plugin-api key MAY be specified to override the
plugin API endpoint.
When populating static properties, the tool MUST:
- Ensure that the loaded plugin has
is_aot_plugin == Trueattribute. It is invalid to use a plugin without that attribute inbuild-requires. - Call
get_supported_configs()to obtain the ordered lists of supported feature name and their values. - Verify that the properties the wheel is built with are compatible per
get_supported_configs(). It is invalid to build a variant wheel with properties that are not permitted bystatic-properties. - Remove
build-requiresandplugin-apikeys from the provider metadata. - Add
static-propertieskey to the provider metadata, populating it using the lists obtained fromget_supported_configs(). - If
get_supported_configs()returned more than one feature name, ensure that all feature names are present indefault-priorities.feature.{namespace}list. Any feature names missing should be appended in the same order as returned byget_supported_configs().
Rationale
Backwards Compatibility
As noted in PEP 825, variant wheels are incompatible with the existing tooling. To avoid disrupting existing workflows, this PEP requires (in build backend support) that build backends do not build variant wheels by default.
The introduction of a new variant table (in pyproject.toml
integration) follows from PEP 518 specifying that such tables are
“reserved for future use by other PEPs”. Nevertheless, it is possible
that overly strict tools will reject such pyproject.toml files as
unsupported, even if the use of wheel variants is not relevant to their
use.
Security Implications
There are no new security implications beyond what is already noted in
PEP 9999. In particular, the use of provider plugins during build
time is not seen as an additional risk, given that the dependencies are
provider by the user directly (for example, in pyproject.toml) and
they are treated equivalently to other build dependencies.
How to Teach This
Reference Implementation
The variantlib project provides a reference implementation of low-level library functions for this PEP. The pep_817_wheel_variants monorepo includes a demo ports of a few build systems.
Rejected Ideas
Open Issues
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
- xx-yyy-2026
- Initial version, split from PEP 817 draft.
Appendices
- :ref:`pep9998-variant-json-schema`
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
Source: https://github.com/python/peps/blob/main/peps/pep-9998.rst
Last modified: 2026-03-02 19:58:16 GMT