diff --git a/docs/options.md b/docs/options.md
index 6b53c8fb..539c81ca 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -64,7 +64,7 @@ When setting the options, you can use shell-style globbing syntax, as per [fnmat
| Python 3.11 | cp311-macosx_x86_64
cp311-macosx_universal2
cp311-macosx_arm64 | cp311-win_amd64
cp311-win32
cp311-win_arm64 | cp311-manylinux_x86_64
cp311-manylinux_i686
cp311-musllinux_x86_64
cp311-musllinux_i686 | cp311-manylinux_aarch64
cp311-manylinux_ppc64le
cp311-manylinux_s390x
cp311-manylinux_armv7l
cp311-manylinux_riscv64
cp311-musllinux_aarch64
cp311-musllinux_ppc64le
cp311-musllinux_s390x
cp311-musllinux_armv7l
cp311-musllinux_riscv64 | | | |
| Python 3.12 | cp312-macosx_x86_64
cp312-macosx_universal2
cp312-macosx_arm64 | cp312-win_amd64
cp312-win32
cp312-win_arm64 | cp312-manylinux_x86_64
cp312-manylinux_i686
cp312-musllinux_x86_64
cp312-musllinux_i686 | cp312-manylinux_aarch64
cp312-manylinux_ppc64le
cp312-manylinux_s390x
cp312-manylinux_armv7l
cp312-manylinux_riscv64
cp312-musllinux_aarch64
cp312-musllinux_ppc64le
cp312-musllinux_s390x
cp312-musllinux_armv7l
cp312-musllinux_riscv64 | | | cp312-pyodide_wasm32 |
| Python 3.13 | cp313-macosx_x86_64
cp313-macosx_universal2
cp313-macosx_arm64 | cp313-win_amd64
cp313-win32
cp313-win_arm64 | cp313-manylinux_x86_64
cp313-manylinux_i686
cp313-musllinux_x86_64
cp313-musllinux_i686 | cp313-manylinux_aarch64
cp313-manylinux_ppc64le
cp313-manylinux_s390x
cp313-manylinux_armv7l
cp313-manylinux_riscv64
cp313-musllinux_aarch64
cp313-musllinux_ppc64le
cp313-musllinux_s390x
cp313-musllinux_armv7l
cp313-musllinux_riscv64 | cp313-android_arm64_v8a
cp313-android_x86_64 | cp313-ios_arm64_iphoneos
cp313-ios_arm64_iphonesimulator
cp313-ios_x86_64_iphonesimulator | cp313-pyodide_wasm32 |
-| Python 3.14 | cp314-macosx_x86_64
cp314-macosx_universal2
cp314-macosx_arm64
cp314t-macosx_x86_64
cp314t-macosx_universal2
cp314t-macosx_arm64 | cp314-win_amd64
cp314-win32
cp314-win_arm64
cp314t-win_amd64
cp314t-win32
cp314t-win_arm64 | cp314-manylinux_x86_64
cp314-manylinux_i686
cp314-musllinux_x86_64
cp314-musllinux_i686
cp314t-manylinux_x86_64
cp314t-manylinux_i686
cp314t-musllinux_x86_64
cp314t-musllinux_i686 | cp314-manylinux_aarch64
cp314-manylinux_ppc64le
cp314-manylinux_s390x
cp314-manylinux_armv7l
cp314-manylinux_riscv64
cp314-musllinux_aarch64
cp314-musllinux_ppc64le
cp314-musllinux_s390x
cp314-musllinux_armv7l
cp314-musllinux_riscv64
cp314t-manylinux_aarch64
cp314t-manylinux_ppc64le
cp314t-manylinux_s390x
cp314t-manylinux_armv7l
cp314t-manylinux_riscv64
cp314t-musllinux_aarch64
cp314t-musllinux_ppc64le
cp314t-musllinux_s390x
cp314t-musllinux_armv7l
cp314t-musllinux_riscv64 | cp314-android_arm64_v8a
cp314-android_x86_64 | cp314-ios_arm64_iphoneos
cp314-ios_arm64_iphonesimulator
cp314-ios_x86_64_iphonesimulator | |
+| Python 3.14 | cp314-macosx_x86_64
cp314-macosx_universal2
cp314-macosx_arm64
cp314t-macosx_x86_64
cp314t-macosx_universal2
cp314t-macosx_arm64 | cp314-win_amd64
cp314-win32
cp314-win_arm64
cp314t-win_amd64
cp314t-win32
cp314t-win_arm64 | cp314-manylinux_x86_64
cp314-manylinux_i686
cp314-musllinux_x86_64
cp314-musllinux_i686
cp314t-manylinux_x86_64
cp314t-manylinux_i686
cp314t-musllinux_x86_64
cp314t-musllinux_i686 | cp314-manylinux_aarch64
cp314-manylinux_ppc64le
cp314-manylinux_s390x
cp314-manylinux_armv7l
cp314-manylinux_riscv64
cp314-musllinux_aarch64
cp314-musllinux_ppc64le
cp314-musllinux_s390x
cp314-musllinux_armv7l
cp314-musllinux_riscv64
cp314t-manylinux_aarch64
cp314t-manylinux_ppc64le
cp314t-manylinux_s390x
cp314t-manylinux_armv7l
cp314t-manylinux_riscv64
cp314t-musllinux_aarch64
cp314t-musllinux_ppc64le
cp314t-musllinux_s390x
cp314t-musllinux_armv7l
cp314t-musllinux_riscv64 | cp314-android_arm64_v8a
cp314-android_x86_64 | cp314-ios_arm64_iphoneos
cp314-ios_arm64_iphonesimulator
cp314-ios_x86_64_iphonesimulator | cp314-pyodide_wasm32 |
| Python 3.15 | cp315-macosx_x86_64
cp315-macosx_universal2
cp315-macosx_arm64
cp315t-macosx_x86_64
cp315t-macosx_universal2
cp315t-macosx_arm64 | cp315-win_amd64
cp315-win32
cp315-win_arm64
cp315t-win_amd64
cp315t-win32
cp315t-win_arm64 | cp315-manylinux_x86_64
cp315-manylinux_i686
cp315-musllinux_x86_64
cp315-musllinux_i686
cp315t-manylinux_x86_64
cp315t-manylinux_i686
cp315t-musllinux_x86_64
cp315t-musllinux_i686 | cp315-manylinux_aarch64
cp315-manylinux_ppc64le
cp315-manylinux_s390x
cp315-manylinux_armv7l
cp315-manylinux_riscv64
cp315-musllinux_aarch64
cp315-musllinux_ppc64le
cp315-musllinux_s390x
cp315-musllinux_armv7l
cp315-musllinux_riscv64
cp315t-manylinux_aarch64
cp315t-manylinux_ppc64le
cp315t-manylinux_s390x
cp315t-manylinux_armv7l
cp315t-manylinux_riscv64
cp315t-musllinux_aarch64
cp315t-musllinux_ppc64le
cp315t-musllinux_s390x
cp315t-musllinux_armv7l
cp315t-musllinux_riscv64 | cp315-android_arm64_v8a
cp315-android_x86_64 | cp315-ios_arm64_iphoneos
cp315-ios_arm64_iphonesimulator
cp315-ios_x86_64_iphonesimulator | |
| PyPy3.9 v7.3 | pp39-macosx_x86_64
pp39-macosx_arm64 | pp39-win_amd64 | pp39-manylinux_x86_64
pp39-manylinux_i686 | pp39-manylinux_aarch64 | | | |
| PyPy3.10 v7.3 | pp310-macosx_x86_64
pp310-macosx_arm64 | pp310-win_amd64 | pp310-manylinux_x86_64
pp310-manylinux_i686 | pp310-manylinux_aarch64 | | | |
diff --git a/unit_test/build_ids_test.py b/unit_test/build_ids_test.py
index 76647623..70a8bcdd 100644
--- a/unit_test/build_ids_test.py
+++ b/unit_test/build_ids_test.py
@@ -1,10 +1,14 @@
import tomllib
+from pathlib import Path
+import pytest
from packaging.version import Version
from cibuildwheel.extra import Printable, dump_python_configurations
from cibuildwheel.util import resources
+OPTIONS_MD = Path(__file__).parents[1] / "docs" / "options.md"
+
def test_compare_configs() -> None:
txt = resources.BUILD_PLATFORMS.read_text()
@@ -18,6 +22,20 @@ def test_compare_configs() -> None:
assert new_txt == txt
+@pytest.mark.skipif(not OPTIONS_MD.is_file(), reason="docs/options.md not present")
+def test_all_identifiers_in_docs_table() -> None:
+ # The build-id table in docs/options.md is hand-maintained; this guards
+ # against forgetting an identifier when build-platforms.toml changes.
+ docs = OPTIONS_MD.read_text()
+ identifiers = [
+ config["identifier"]
+ for configs in resources.read_all_configs().values()
+ for config in configs
+ ]
+ missing = [i for i in identifiers if i not in docs]
+ assert not missing, f"Missing from docs/options.md build-id table: {missing}"
+
+
def test_dump_with_Version() -> None:
# MyPy doesn't understand deeply nested dicts correctly
example: dict[str, dict[str, list[dict[str, Printable]]]] = {