From 41b837ee0ff08366effae6bd7ab5c942d6016f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 14:54:06 +0100 Subject: [PATCH 01/27] Boilerplate for 9998 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 peps/pep-9998.rst diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst new file mode 100644 index 00000000000..ccd7165d2c1 --- /dev/null +++ b/peps/pep-9998.rst @@ -0,0 +1,111 @@ +PEP: 9998 +Title: Wheel Variants: Building +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, 9999 +Created: 02-Mar-2026 +Post-History: Pending + +Abstract +======== + + +Motivation +========== + + +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`. + + +Rationale +========= + + +Backwards Compatibility +======================= + + +Security Implications +===================== + + +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. From 54f04d2d48ea21751cd523eb59beeb4362a35a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 17:05:33 +0100 Subject: [PATCH 02/27] Some spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 117 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index ccd7165d2c1..2a4f0c864f9 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -26,6 +26,11 @@ 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 ============= @@ -38,6 +43,118 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 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 ``variants`` dictionary +- there are entries in ``default-priorities.namespace`` and + ``providers`` dictionaries for all namespaces found in variant + properties +- when ``providers.{namespace}.static-properties`` specifies more than + one feature name, there are entries for all feature names in + ``default-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.toml`` file, as + described in `pyproject.toml integration`_ +- the available variants are described in ``variant.variants`` table + in said file +- the ``config_settings`` dictionary of wheel building hooks accepts a + ``variant-label`` key that enables building the specified variant from + ``variant.variants`` dictionary + + +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, 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-priorities`` dictionary is used to seed + ``default-priorities`` in the wheels +- the ``providers`` dictionary is used to seed ``providers`` in the + wheels and to query providers at build time, if necessary +- the ``variants`` dictionary MAY be used to provide a mapping from + variant labels to variant properties, to facilitate requesting a build + of a specific variant + +Tools MAY provide additional methods of providing the necessary variant +metadata. + + +Example +''''''' + +.. code:: toml + + [variant] + $schema = + + [variant.default-priorities] + # prefer CPU features over BLAS/LAPACK variants + namespace = ["x86_64", "aarch64", "blas_lapack"] + + # prefer aarch64 version and x86_64 level features over other features + # (specific CPU extensions like "sse4.1") + feature.aarch64 = ["version"] + feature.x86_64 = ["level"] + + # prefer x86-64-v3 and then older (even if CPU is newer) + property.x86_64.level = ["v3", "v2", "v1"] + + [variant.providers.aarch64] + # example using different package based on Python version + requires = [ + "provider-variant-aarch64 >=0.0.1; python_version >= '3.12'", + "legacy-provider-variant-aarch64 >=0.0.1; python_version < '3.12'", + ] + # use only on aarch64/arm machines + enable-if = "platform_machine == 'aarch64' or 'arm' in platform_machine" + plugin-api = "provider_variant_aarch64.plugin:AArch64Plugin" + + [variant.providers.x86_64] + requires = ["provider-variant-x86-64 >=0.0.1"] + # use only on x86_64 machines + enable-if = "platform_machine == 'x86_64' or platform_machine == 'AMD64'" + plugin-api = "provider_variant_x86_64.plugin:X8664Plugin" + + [variant.providers.blas_lapack] + # plugin-api inferred from requires + requires = ["blas-lapack-variant-provider"] + # plugin used only when building package, properties will be inlined + # into variant.json + install-time = false + + +Extensions to provider plugin API +--------------------------------- + + Rationale ========= From e3ae2f758149850ad42faebd12a7ae6e54afc3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 17:35:01 +0100 Subject: [PATCH 03/27] Finish the TOML example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 75 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 2a4f0c864f9..fe3d882b11f 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -101,7 +101,7 @@ variant wheels. More specifically: wheels and to query providers at build time, if necessary - the ``variants`` dictionary MAY be used to provide a mapping from variant labels to variant properties, to facilitate requesting a build - of a specific variant + of a specific variant (``null`` variant does not need to be specified) Tools MAY provide additional methods of providing the necessary variant metadata. @@ -113,42 +113,71 @@ Example .. code:: toml [variant] - $schema = + "$schema" = "https://variants-schema.wheelnext.dev/peps/9999/v0.2.0.json" [variant.default-priorities] - # prefer CPU features over BLAS/LAPACK variants + # 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"] - # prefer aarch64 version and x86_64 level features over other features - # (specific CPU extensions like "sse4.1") - feature.aarch64 = ["version"] - feature.x86_64 = ["level"] + # OPTIONAL: makes "library" the most important feature in "blas_lapack" + # namespace + feature.blas_lapack = ["library"] - # prefer x86-64-v3 and then older (even if CPU is newer) - property.x86_64.level = ["v3", "v2", "v1"] + # 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] - # example using different package based on Python version + # 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; python_version >= '3.12'", - "legacy-provider-variant-aarch64 >=0.0.1; python_version < '3.12'", + "provider-variant-aarch64 >=0.0.1", ] - # use only on aarch64/arm machines - enable-if = "platform_machine == 'aarch64' or 'arm' in platform_machine" + # 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] + # Specifies compatible properties. No install-time querying is + # necessary. + static-properties.library = ["accelerate", "openblas", "mkl"] + # OPTIONAL: unused since "static-properties" are provided. + requires = ["blas-lapack-variant-provider"] + [variant.providers.x86_64] - requires = ["provider-variant-x86-64 >=0.0.1"] - # use only on x86_64 machines - enable-if = "platform_machine == 'x86_64' or platform_machine == 'AMD64'" + # 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" - [variant.providers.blas_lapack] - # plugin-api inferred from requires - requires = ["blas-lapack-variant-provider"] - # plugin used only when building package, properties will be inlined - # into variant.json - install-time = false + # 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 From 371e32d4316a50c3f9b1ec30a565dd32e1a15f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 18:12:51 +0100 Subject: [PATCH 04/27] Plugin interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index fe3d882b11f..22d3e1704d3 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -183,6 +183,64 @@ Example Extensions to provider plugin API --------------------------------- +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). + + +Example implementation +'''''''''''''''''''''' + +.. code:: python + + 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, + ), + ] + Rationale ========= From 54b9a81d5d33da5af7462ea84bac23769e3c70d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 18:16:28 +0100 Subject: [PATCH 05/27] Add prefaces to examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 22d3e1704d3..289bf267eca 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -110,6 +110,10 @@ metadata. 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: + .. code:: toml [variant] @@ -200,6 +204,9 @@ ordering). Example implementation '''''''''''''''''''''' +The example plugin implementation from :pep:`9999` can be extended in +the following way: + .. code:: python from dataclasses import dataclass From 166311816f184de4aae07fa5689754e6d6bed2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 18:44:12 +0100 Subject: [PATCH 06/27] Onwards! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 289bf267eca..b14968c79b3 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -82,6 +82,11 @@ following interface for building variant wheels: - the ``config_settings`` dictionary of wheel building hooks accepts a ``variant-label`` key that enables building the specified variant from ``variant.variants`` dictionary +- 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 @@ -256,10 +261,28 @@ 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 ================= From 75e75ffd8e2a84a0ad9b5e0cc9d135d63caadd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 2 Mar 2026 20:58:16 +0100 Subject: [PATCH 07/27] Draft the `build-requires` idea MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 118 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 9 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index b14968c79b3..58282f6848b 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -95,10 +95,10 @@ 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, 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: +: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-priorities`` dictionary is used to seed ``default-priorities`` in the wheels @@ -112,6 +112,24 @@ 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 + :ref:`dependency specifiers `, same as + ``requires`` but used only to populate ``static-properties`` at 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 ''''''' @@ -154,11 +172,8 @@ could look like the following: plugin-api = "provider_variant_aarch64.plugin:AArch64Plugin" [variant.providers.blas_lapack] - # Specifies compatible properties. No install-time querying is - # necessary. - static-properties.library = ["accelerate", "openblas", "mkl"] - # OPTIONAL: unused since "static-properties" are provided. - requires = ["blas-lapack-variant-provider"] + # 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 @@ -192,6 +207,10 @@ could look like the following: 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: @@ -205,6 +224,13 @@ 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 to ``True``, then all valid + feature names and values are always supported. In that case, the + ``get_supported_configs()`` function MUST always return the same value + as ``get_all_configs()`` (modulo ordering). + Example implementation '''''''''''''''''''''' @@ -254,6 +280,80 @@ the following way: ] +Example AoT plugin implementation +''''''''''''''''''''''''''''''''' + +.. code:: python + + 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-requires`` key is used in place of ``requires`` key +- 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: + +1. Ensure that the loaded plugin has ``is_aot_plugin == True`` + attribute. It is invalid to use a plugin without that attribute in + ``build-requires``. + +2. Call ``get_supported_configs()`` to obtain the ordered lists of + supported feature name and their values. + +3. 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 by ``static-properties``. + +4. Remove ``build-requires`` and ``plugin-api`` keys from the provider + metadata. + +5. Add ``static-properties`` key to the provider metadata, populating it + using the lists obtained from ``get_supported_configs()``. + +6. If ``get_supported_configs()`` returned more than one feature name, + ensure that all feature names are present in + ``default-priorities.feature.{namespace}`` list. Any feature names + missing should be appended in the same order as returned by + ``get_supported_configs()``. + + Rationale ========= From 843cbf732f978223f823595ed688c3642b083e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 3 Mar 2026 11:12:57 +0100 Subject: [PATCH 08/27] List changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 58282f6848b..29fdc4f20cd 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -428,6 +428,15 @@ Change History - xx-yyy-2026 - Initial version, split from :pep:`817` draft. + - A specific build backend interface for building variant wheels was + proposed. + - The ``variants`` dictionary is now permitted in ``pyproject.toml`` + metadata, and it can be used to define available variants to build. + - A new ``build-requires`` key was introduced in provider metadata. + It can only be used ``pyproject.toml`` and it is used to populate + ``static-properties`` from a provider plugin at build time. It + replaced the previous combination of ``install-time = false`` with + non-empty ``requires``. Appendices From 0502fc9a3c6dd6af15b0b01a3512b76b05739542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 3 Mar 2026 16:11:36 +0100 Subject: [PATCH 09/27] Rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 29fdc4f20cd..4ea4d2485c1 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -357,6 +357,36 @@ When populating static properties, the tool MUST: Rationale ========= +This PEP considers a variety of possible tools that may emit variant +wheels, including build backends, build frontends and tools dedicated to +converting regular wheels into variant wheels. A standardized interfaces +for project tree metadata and build backends are proposed to offer +consistent user experience, but are by no means mandatory. The standard +``pyproject.toml`` file is used to avoid spreading the project +configuration across multiple files, and the same schema as for variant +metadata is used. + +When using build backends, using ``get_requires*()`` hooks for exposing +provider plugin dependencies is recommended, to reuse the existing logic +for creating isolated environments and installing packages. This implies +that provider plugin packages are treated as regular build dependencies. + +The provider plugin API is extended to enable verifying whether the +properties specified for a variant are correct. To this purpose, a +``get_all_configs()`` function is introduced that is symmetric to +``get_supported_configs()`` introduced in :pep:`9999`. However, the +build tools are not required to perform this validation, as it requires +installing and querying provider plugins. + +Furthermore, this PEP permits populating ``static-properties`` in +provider information using provider plugins. This needs both to be +supported by the plugin in question, and explicitly enabled in the +package, through using ``build-requires`` in place of ``requires``. This +can be used to ensure that multiple packages use the same properties for +the same features, such as BLAS/LAPACK library names. The process is +handled entirely at build time, and the resulting wheel is fully +compliant with :pep:`9999`. + Backwards Compatibility ======================= From 5c592a48c3cdac31cb3e131769af89d431dfa2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 3 Mar 2026 18:55:55 +0100 Subject: [PATCH 10/27] Rationalize redundancy of all/supported getters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 4ea4d2485c1..f12e1527da9 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -387,6 +387,12 @@ the same features, such as BLAS/LAPACK library names. The process is handled entirely at build time, and the resulting wheel is fully compliant with :pep:`9999`. +Technically, the presence of both ``get_supported_configs()`` and +``get_all_configs()`` for AoT plugins is redundant: both must return the +same values, except that the latter may return them in any order. +However, the specification requires both to keep a single interface for +all provider plugins. + Backwards Compatibility ======================= From 8a42f1b862b6be8ee9d360898bbaf8ef4e5b9abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 3 Mar 2026 19:01:06 +0100 Subject: [PATCH 11/27] HtTT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index f12e1527da9..278f2f02291 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -423,6 +423,12 @@ they are treated equivalently to other build dependencies. How to Teach This ================= +This PEP is primarily oriented towards package maintainers. The primary +source of information will be the documentation of build backends and +other tools that support building variant wheels. The documentation +present on ``packaging.python.org`` will also be updated for the +recommended build backend interface. + Reference Implementation ======================== From b25b9e14ec5db6a400b4efe22946ca63eef6aff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 9 Mar 2026 14:12:51 +0100 Subject: [PATCH 12/27] Update the install-time plugin example not to use class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 45 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 278f2f02291..808a23d6a53 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -255,29 +255,27 @@ the following way: _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, - ), - ] + def get_supported_configs() -> list[VariantFeatureConfig]: + ... # same as in PEP 9999 + + + 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 @@ -307,6 +305,7 @@ Example AoT plugin implementation ), ] + def get_all_configs() -> list[_VariantFeatureConfig]: return get_supported_configs() From 74283fafeaccbb0c2f1117431f1b6c64069d4c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 9 Mar 2026 14:14:41 +0100 Subject: [PATCH 13/27] =?UTF-8?q?`is=5Faot=5Fplugin`=20=E2=86=92=20`all=5F?= =?UTF-8?q?properties=5Fcompatible`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 808a23d6a53..cb262106bdb 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -226,10 +226,10 @@ ordering). Additionally, the following OPTIONAL attribute is added: -- ``is_aot_plugin: bool = False``; if it set to ``True``, then all valid - feature names and values are always supported. In that case, the - ``get_supported_configs()`` function MUST always return the same value - as ``get_all_configs()`` (modulo ordering). +- ``all_properties_compatible: bool = False``; if it set to ``True``, + then all valid feature names and values are always supported. In that + case, the ``get_supported_configs()`` function MUST always return the + same value as ``get_all_configs()`` (modulo ordering). Example implementation @@ -293,7 +293,7 @@ Example AoT plugin implementation multi_value: bool - is_aot_plugin = True + all_properties_compatible = True def get_supported_configs() -> list[_VariantFeatureConfig]: @@ -329,9 +329,9 @@ plugin API endpoint. When populating static properties, the tool MUST: -1. Ensure that the loaded plugin has ``is_aot_plugin == True`` - attribute. It is invalid to use a plugin without that attribute in - ``build-requires``. +1. Ensure that the loaded plugin has ``all_properties_compatible == + True`` attribute. It is invalid to use a plugin without that + attribute in ``build-requires``. 2. Call ``get_supported_configs()`` to obtain the ordered lists of supported feature name and their values. From 678238f2aa5d0de7bc1c1109e6dbe38b98929c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 9 Mar 2026 14:15:59 +0100 Subject: [PATCH 14/27] Remove remaining "AoT"s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index cb262106bdb..7d0744356df 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -278,8 +278,8 @@ the following way: ] -Example AoT plugin implementation -''''''''''''''''''''''''''''''''' +Example implementation of a plugin suitable for build-time use +'''''''''''''''''''''''''''''''''----------------------------- .. code:: python @@ -387,10 +387,10 @@ handled entirely at build time, and the resulting wheel is fully compliant with :pep:`9999`. Technically, the presence of both ``get_supported_configs()`` and -``get_all_configs()`` for AoT plugins is redundant: both must return the -same values, except that the latter may return them in any order. -However, the specification requires both to keep a single interface for -all provider plugins. +``get_all_configs()`` when all properties are compatible is redundant: +both must return the same values, except that the latter may return them +in any order. However, the specification requires both to keep a single +interface for all provider plugins. Backwards Compatibility From eb42b43318882e75f4e767363cc7008ceb87b9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 9 Mar 2026 14:16:36 +0100 Subject: [PATCH 15/27] =?UTF-8?q?supported=20=E2=86=92=20compatible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 7d0744356df..74cac71c4da 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -227,7 +227,7 @@ ordering). Additionally, the following OPTIONAL attribute is added: - ``all_properties_compatible: bool = False``; if it set to ``True``, - then all valid feature names and values are always supported. In that + then all valid feature names and values are always compatible. In that case, the ``get_supported_configs()`` function MUST always return the same value as ``get_all_configs()`` (modulo ordering). From 191f6c67300c42cf01b41eca9bc9cb735b2097ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 12 Mar 2026 15:43:31 +0100 Subject: [PATCH 16/27] Address some of the review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 74cac71c4da..e8bc84d97e2 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -192,14 +192,14 @@ could look like the following: # "x8664v3_openblas" label corresponds to: # - blas_lapack :: library :: openblas # - x86_64 :: level :: v3 - [variant.variants.x8664_v3_openblas] + [variant.variants.x8664v3_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] + [variant.variants.x8664v4_mkl] blas_lapack.library = ["mkl"] x86_64.level = ["v4"] @@ -214,7 +214,7 @@ specifies either ``requires`` or ``build-requires`` key, per the The plugin interface specified in :pep:`9999` is extended by adding the following REQUIRED function: -- ``get_all_config() -> list[VariantFeatureConfigType]`` that returns a +- ``get_all_configs() -> 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 @@ -403,10 +403,8 @@ 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. +"reserved for future use by other PEPs". Therefore, introducing the +``[variant]`` table is backwards compatible. Security Implications From 267f4cc008f67c14b4be88dbbc1be1fbb2a4c9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 12 Mar 2026 16:05:37 +0100 Subject: [PATCH 17/27] Update labels in example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index e8bc84d97e2..0c6f22532b7 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -189,17 +189,17 @@ could look like the following: # RECOMMENDED: variants that can be built # --------------------------------------- - # "x8664v3_openblas" label corresponds to: + # "x86_64_v3_openblas" label corresponds to: # - blas_lapack :: library :: openblas # - x86_64 :: level :: v3 - [variant.variants.x8664v3_openblas] + [variant.variants.x86_64_v3_openblas] blas_lapack.library = ["openblas"] x86_64.level = ["v3"] - # "x8664v4_mkl" label corresponds to: + # "x86_64_v4_mkl" label corresponds to: # - blas_lapack :: library :: mkl # - x86_64 :: level :: v4 - [variant.variants.x8664v4_mkl] + [variant.variants.x86_64_v4_mkl] blas_lapack.library = ["mkl"] x86_64.level = ["v4"] From 7b1ee2ace6f21733e9bbd3b0a09531a38d1796c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 9 Sep 2026 17:23:22 +0200 Subject: [PATCH 18/27] Update change history for `is_aot_plugin` rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 0c6f22532b7..25505ecf115 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -476,6 +476,8 @@ Change History ``static-properties`` from a provider plugin at build time. It replaced the previous combination of ``install-time = false`` with non-empty ``requires``. + - The ``is_aot_plugin`` attribute of the plugin interface has been + renamed to ``all_properties_compatible``. Appendices From 780bb24c851768ed0a2d64595a9583a5dbfe06c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 16 Sep 2026 12:50:50 +0200 Subject: [PATCH 19/27] Add protocol for plugin interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 25505ecf115..04071e67b9a 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -231,6 +231,46 @@ Additionally, the following OPTIONAL attribute is added: case, the ``get_supported_configs()`` function MUST always return the same value as ``get_all_configs()`` (modulo ordering). +The resulting interface can be described using the following protocol: + +.. code:: python + + from abc import abstractmethod + from typing import Protocol + + + class PluginType(Protocol): + # Note: properties are used here for docstring purposes, these must + # be actually implemented as attributes. + + @property + def all_properties_compatible(self) -> bool: + """ + Is this plugin valid for use with `build-requires`? + + If this is True, then `get_supported_configs()` must always + return the same values, irrespective of the platform used. + This permits the plugin to be used via `build-requires`, + where the supported properties are recorded at build time. + + If the value of `get_supported_configs()` may change in any way + depending on the platform used, then it must be False + (the default). + """ + return False + + @classmethod + @abstractmethod + def get_all_configs(cls) -> list[VariantFeatureConfigType]: + """Get all valid configs for the plugin""" + raise NotImplementedError + + @classmethod + @abstractmethod + def get_supported_configs(cls) -> list[VariantFeatureConfigType]: + """Get ordered lists of compatible features and their values""" + raise NotImplementedError + Example implementation '''''''''''''''''''''' From a62c1ac323bdb6deb7eec6ed3f3e5d367594822b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 16 Sep 2026 12:59:22 +0200 Subject: [PATCH 20/27] Add a schema to the building PEP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 4 +- .../pep-9998/appendix-variant-json-schema.rst | 11 ++ peps/pep-9998/variant-schema-0.3.0.json | 146 ++++++++++++++++++ 3 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 peps/pep-9998/appendix-variant-json-schema.rst create mode 100644 peps/pep-9998/variant-schema-0.3.0.json diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 04071e67b9a..f1cd626ce28 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -523,9 +523,7 @@ Change History Appendices ========== -:: - - - :ref:`pep9998-variant-json-schema` +- :ref:`pep9998-variant-json-schema` Copyright diff --git a/peps/pep-9998/appendix-variant-json-schema.rst b/peps/pep-9998/appendix-variant-json-schema.rst new file mode 100644 index 00000000000..55f82bf8e44 --- /dev/null +++ b/peps/pep-9998/appendix-variant-json-schema.rst @@ -0,0 +1,11 @@ +:orphan: + +.. _pep9998-variant-json-schema: + +Appendix: JSON Schema for Variant Metadata +========================================== + +.. literalinclude:: variant-schema-0.3.0.json + :language: json + :linenos: + :name: variant-json-schema diff --git a/peps/pep-9998/variant-schema-0.3.0.json b/peps/pep-9998/variant-schema-0.3.0.json new file mode 100644 index 00000000000..bd966b42040 --- /dev/null +++ b/peps/pep-9998/variant-schema-0.3.0.json @@ -0,0 +1,146 @@ +{ + "$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": { + "build-requires": { + "description": "Dependency specifiers for how to install the plugin at build time", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 0, + "uniqueItems": true + }, + "feature-order": { + "description": "Feature order for static-properties", + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "minItems": 0, + "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": 0, + "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, + "anyOf": [ + {"required": ["build-requires"]}, + {"required": ["requires"]}, + {"required": ["static-properties"]} + ] + } + }, + "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 70e1a47781a834c43e509472823f81c4dc34e0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 16 Sep 2026 13:17:21 +0200 Subject: [PATCH 21/27] Sync and update the constraints in the schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998/variant-schema-0.3.0.json | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/peps/pep-9998/variant-schema-0.3.0.json b/peps/pep-9998/variant-schema-0.3.0.json index bd966b42040..b85b86d097f 100644 --- a/peps/pep-9998/variant-schema-0.3.0.json +++ b/peps/pep-9998/variant-schema-0.3.0.json @@ -54,7 +54,7 @@ "type": "string", "pattern": "^[a-z0-9_]+$" }, - "minItems": 0, + "minItems": 1, "uniqueItems": true }, "optional": { @@ -73,7 +73,7 @@ "type": "string", "minLength": 1 }, - "minItems": 0, + "minItems": 1, "uniqueItems": true }, "static-properties": { @@ -95,10 +95,19 @@ } }, "additionalProperties": false, - "anyOf": [ - {"required": ["build-requires"]}, - {"required": ["requires"]}, - {"required": ["static-properties"]} + "oneOf": [ + { + "required": ["requires"], + "not": {"required": ["feature-order"]} + }, + { + "required": ["build-requires"], + "not": {"required": ["feature-order"]} + }, + { + "required": ["static-properties"], + "not": {"required": ["plugin-api"]} + } ] } }, From c5ca931ec02a8d7d2807eada5fced2d1603e588f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 16 Sep 2026 13:39:39 +0200 Subject: [PATCH 22/27] Clarify the provider metadata extension section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index f1cd626ce28..d5d7283b4e5 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -124,10 +124,26 @@ The provider information dictionaries used as values in the 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. +If the ``build-requires`` key is specified, then ``feature-order``, +``requires`` and ``static-properties`` keys MUST NOT be present. + +The ``build-requires`` key is only valid in the ``pyproject.toml`` file. +In a variant wheel built from that file, the ``build-requires`` and +``plugin-api`` keys MUST be removed, and ``static-properties`` MUST be +added instead. If the resulting ``static-properties`` dictionary lists +more than one feature, then the ``feature-order`` key MUST be added as +well. + +Consequently, every provider metadata MUST use exactly one of these +three key combinations: + +1. ``requires``, and optionally ``plugin-api`` +2. ``build-requires``, and optionally ``plugin-api`` (valid only in + ``pyproject.toml``) +3. ``static-properties``, and optionally ``feature-order`` + +In addition to that, the ``optional`` key is always valid. Further rules +on when the optional keys are required are specified in :pep:`9999`. Example From 45391314bce425c6f8cef8d9a5efcc524cc64a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 16 Sep 2026 17:05:00 +0200 Subject: [PATCH 23/27] Revert "Sync and update the constraints in the schema" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e599dbcf39b304c7a989e34ecb016579107fa6ce. Signed-off-by: Michał Górny --- peps/pep-9998/variant-schema-0.3.0.json | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/peps/pep-9998/variant-schema-0.3.0.json b/peps/pep-9998/variant-schema-0.3.0.json index b85b86d097f..bd966b42040 100644 --- a/peps/pep-9998/variant-schema-0.3.0.json +++ b/peps/pep-9998/variant-schema-0.3.0.json @@ -54,7 +54,7 @@ "type": "string", "pattern": "^[a-z0-9_]+$" }, - "minItems": 1, + "minItems": 0, "uniqueItems": true }, "optional": { @@ -73,7 +73,7 @@ "type": "string", "minLength": 1 }, - "minItems": 1, + "minItems": 0, "uniqueItems": true }, "static-properties": { @@ -95,19 +95,10 @@ } }, "additionalProperties": false, - "oneOf": [ - { - "required": ["requires"], - "not": {"required": ["feature-order"]} - }, - { - "required": ["build-requires"], - "not": {"required": ["feature-order"]} - }, - { - "required": ["static-properties"], - "not": {"required": ["plugin-api"]} - } + "anyOf": [ + {"required": ["build-requires"]}, + {"required": ["requires"]}, + {"required": ["static-properties"]} ] } }, From b63ce1662aab09bca591287cd56b720d801074d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Wed, 16 Sep 2026 17:05:03 +0200 Subject: [PATCH 24/27] Revert "Add a schema to the building PEP" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4109c21b0fd5e73028b39cd5ae7eb425abb5a00d. Signed-off-by: Michał Górny --- peps/pep-9998.rst | 4 +- .../pep-9998/appendix-variant-json-schema.rst | 11 -- peps/pep-9998/variant-schema-0.3.0.json | 146 ------------------ 3 files changed, 3 insertions(+), 158 deletions(-) delete mode 100644 peps/pep-9998/appendix-variant-json-schema.rst delete mode 100644 peps/pep-9998/variant-schema-0.3.0.json diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index d5d7283b4e5..a1584fed8ef 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -539,7 +539,9 @@ Change History Appendices ========== -- :ref:`pep9998-variant-json-schema` +:: + + - :ref:`pep9998-variant-json-schema` Copyright diff --git a/peps/pep-9998/appendix-variant-json-schema.rst b/peps/pep-9998/appendix-variant-json-schema.rst deleted file mode 100644 index 55f82bf8e44..00000000000 --- a/peps/pep-9998/appendix-variant-json-schema.rst +++ /dev/null @@ -1,11 +0,0 @@ -:orphan: - -.. _pep9998-variant-json-schema: - -Appendix: JSON Schema for Variant Metadata -========================================== - -.. literalinclude:: variant-schema-0.3.0.json - :language: json - :linenos: - :name: variant-json-schema diff --git a/peps/pep-9998/variant-schema-0.3.0.json b/peps/pep-9998/variant-schema-0.3.0.json deleted file mode 100644 index bd966b42040..00000000000 --- a/peps/pep-9998/variant-schema-0.3.0.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "$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": { - "build-requires": { - "description": "Dependency specifiers for how to install the plugin at build time", - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "minItems": 0, - "uniqueItems": true - }, - "feature-order": { - "description": "Feature order for static-properties", - "type": "array", - "items": { - "type": "string", - "pattern": "^[a-z0-9_]+$" - }, - "minItems": 0, - "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": 0, - "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, - "anyOf": [ - {"required": ["build-requires"]}, - {"required": ["requires"]}, - {"required": ["static-properties"]} - ] - } - }, - "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 d4a8ee3512e0ceaa37cb1c6b08f5946df4a4a8c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 13:18:35 +0200 Subject: [PATCH 25/27] 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-9998.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index a1584fed8ef..5f9cf502ef3 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -175,7 +175,7 @@ could look like the following: # REQUIRED: variant providers # --------------------------- - # (keys must matchdefault-priorities.namespace) + # (keys must match default-priorities.namespace) [variant.providers.aarch64] # Specifies provider plugin package. REQUIRED since there is no @@ -335,7 +335,7 @@ the following way: Example implementation of a plugin suitable for build-time use -'''''''''''''''''''''''''''''''''----------------------------- +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' .. code:: python From ff2ccd430bf373998a192f711ac46156be222e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Tue, 22 Sep 2026 13:20:51 +0200 Subject: [PATCH 26/27] Replace obsolete `default-priorities` keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 5f9cf502ef3..470a50de15e 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -57,7 +57,7 @@ they MUST ensure that the produced wheels conform to :pep:`825` and properties - when ``providers.{namespace}.static-properties`` specifies more than one feature name, there are entries for all feature names in - ``default-priorities.feature.{namespace}`` + ``providers.{namespace}.feature-order`` Additionally, tools MAY verify that: @@ -165,14 +165,6 @@ could look like the following: # 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 match default-priorities.namespace) @@ -403,10 +395,8 @@ When populating static properties, the tool MUST: using the lists obtained from ``get_supported_configs()``. 6. If ``get_supported_configs()`` returned more than one feature name, - ensure that all feature names are present in - ``default-priorities.feature.{namespace}`` list. Any feature names - missing should be appended in the same order as returned by - ``get_supported_configs()``. + set ``providers.{namespace}.feature-order`` to the ordered list of + features. Rationale From df2b8e44326b0b993c30e7ad53ad3a0fe2d13b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Mon, 28 Sep 2026 18:14:01 +0200 Subject: [PATCH 27/27] Update for `builtin`, clarify `plugin-api`, sync example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- peps/pep-9998.rst | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/peps/pep-9998.rst b/peps/pep-9998.rst index 470a50de15e..2f0964f4b42 100644 --- a/peps/pep-9998.rst +++ b/peps/pep-9998.rst @@ -124,8 +124,12 @@ The provider information dictionaries used as values in the time, as explained in `populating static properties from provider plugins`_. -If the ``build-requires`` key is specified, then ``feature-order``, -``requires`` and ``static-properties`` keys MUST NOT be present. +The ``plugin-api`` key is used with ``build-requires`` in the same way +as it is used with ``requires``. + +If the ``build-requires`` key is specified, then the ``builtin``, +``feature-order``, ``requires`` and ``static-properties`` keys MUST NOT +be present. The ``build-requires`` key is only valid in the ``pyproject.toml`` file. In a variant wheel built from that file, the ``build-requires`` and @@ -135,12 +139,13 @@ more than one feature, then the ``feature-order`` key MUST be added as well. Consequently, every provider metadata MUST use exactly one of these -three key combinations: +four key combinations: 1. ``requires``, and optionally ``plugin-api`` 2. ``build-requires``, and optionally ``plugin-api`` (valid only in ``pyproject.toml``) 3. ``static-properties``, and optionally ``feature-order`` +4. ``builtin`` In addition to that, the ``optional`` key is always valid. Further rules on when the optional keys are required are specified in :pep:`9999`. @@ -162,30 +167,39 @@ could look like the following: # 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"] + # library: The "abi" namespace using a builtin provider is listed last, + # it does not influence ordering here. + namespace = ["x86_64", "aarch64", "blas_lapack", "abi"] # REQUIRED: variant providers # --------------------------- # (keys must match default-priorities.namespace) [variant.providers.aarch64] - # Specifies provider plugin package. REQUIRED since there is no - # "static-properties" -- plugin will be queried at install time. + # Specifies provider plugin package; the plugin will be queried + # at install time. REQUIRED, mutually exclusive with the other required + # keys. 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" + # "plugin-api" is OPTIONAL here. It is inferred from "requires". + # plugin-api = "provider_variant_aarch64" + + [variant.providers.abi] + # Specifies that this a builtin provider. REQUIRED, mutually exclusive + # with the other required keys. + builtin = "abi_dependency" [variant.providers.blas_lapack] - # REQUIRED: used to populate "static-properties". + # Specifies that "static-properties" are to be populated from a provider + # plugin package; it will be queried at build time. REQUIRED, mutually + # exclusive with the other required keys. 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. + # Specifies provider plugin package; the plugin will be queried + # at install time. REQUIRED, mutually exclusive with the other required + # keys. requires = [ "provider-variant-x86-64 >=0.0.1; python_version >= '3.12'", "legacy-provider-variant-x86-64 >=0.0.1; python_version < '3.12'",