Add a Python matrix to ensure the bindings build on all supported versions (#1871)

Also contains a run of `pre-commit autoupdate`, and a bump of cibuildwheel
to its latest tag for CPython 3.13 support.

But, since we build for 3.10+ with SABI from 3.12 onwards, we don't even
need a dedicated Python 3.13 build job or toolchain - the wheels from 3.12
can be reused.

Simplifies some version-dependent logic around assembling the bazel
build command in setup.py, and fixes a possible unbound local error in
the toolchain patch context manager.
diff --git a/.github/workflows/test_bindings.yml b/.github/workflows/test_bindings.yml
index 436a8f9..b6ac9be 100644
--- a/.github/workflows/test_bindings.yml
+++ b/.github/workflows/test_bindings.yml
@@ -8,23 +8,23 @@
 
 jobs:
   python_bindings:
-    name: Test GBM Python bindings on ${{ matrix.os }}
+    name: Test GBM Python ${{ matrix.python-version }} bindings on ${{ matrix.os }}
     runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
       matrix:
         os: [ ubuntu-latest, macos-latest, windows-latest ]
+        python-version: [ "3.10", "3.11", "3.12", "3.13" ]
 
     steps:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 0
-      - name: Set up Python 3.11
+      - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v5
         with:
-          python-version: 3.11
+          python-version: ${{ matrix.python-version }}
       - name: Install GBM Python bindings on ${{ matrix.os }}
         run: python -m pip install .
-      - name: Run bindings example on ${{ matrix.os }}
-        run:
-          python bindings/python/google_benchmark/example.py
+      - name: Run example on ${{ matrix.os }} under Python ${{ matrix.python-version }}
+        run: python bindings/python/google_benchmark/example.py
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 7544b24..b463ff8 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -53,7 +53,7 @@
           platforms: all
 
       - name: Build wheels on ${{ matrix.os }} using cibuildwheel
-        uses: pypa/cibuildwheel@v2.20
+        uses: pypa/cibuildwheel@v2.21.3
         env:
           CIBW_BUILD: "cp310-* cp311-* cp312-*"
           CIBW_BUILD_FRONTEND: "build[uv]"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ef13c1d..2a51592 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,17 +1,17 @@
 repos:
   -   repo: https://github.com/keith/pre-commit-buildifier
-      rev: 7.1.2
+      rev: 7.3.1
       hooks:
       -   id: buildifier
       -   id: buildifier-lint
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.11.1
+    rev: v1.13.0
     hooks:
       - id: mypy
         types_or: [ python, pyi ]
         args: [ "--ignore-missing-imports", "--scripts-are-modules" ]
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.6.1
+    rev: v0.7.2
     hooks:
       - id: ruff
         args: [ --fix, --exit-non-zero-on-fix ]
diff --git a/setup.py b/setup.py
index 1e4c0db..238d9d8 100644
--- a/setup.py
+++ b/setup.py
@@ -3,6 +3,7 @@
 import platform
 import re
 import shutil
+import sys
 from pathlib import Path
 from typing import Any, Generator
 
@@ -15,8 +16,7 @@
 
 # hardcoded SABI-related options. Requires that each Python interpreter
 # (hermetic or not) participating is of the same major-minor version.
-version_tuple = tuple(int(i) for i in platform.python_version_tuple())
-py_limited_api = version_tuple >= (3, 12)
+py_limited_api = sys.version_info >= (3, 12)
 options = {"bdist_wheel": {"py_limited_api": "cp312"}} if py_limited_api else {}
 
 
@@ -43,10 +43,10 @@
         return "python.toolchain(" + callargs + ")"
 
     CIBW_LINUX = is_cibuildwheel() and IS_LINUX
+    module_bazel = Path("MODULE.bazel")
+    content: str = module_bazel.read_text()
     try:
         if CIBW_LINUX:
-            module_bazel = Path("MODULE.bazel")
-            content: str = module_bazel.read_text()
             module_bazel.write_text(
                 re.sub(
                     r"python.toolchain\(([\w\"\s,.=]*)\)",
@@ -92,10 +92,16 @@
     def bazel_build(self, ext: BazelExtension) -> None:
         """Runs the bazel build to create the package."""
         temp_path = Path(self.build_temp)
-        # omit the patch version to avoid build errors if the toolchain is not
-        # yet registered in the current @rules_python version.
-        # patch version differences should be fine.
-        python_version = ".".join(platform.python_version_tuple()[:2])
+        if py_limited_api:
+            # We only need to know the minimum ABI version,
+            # since it is stable across minor versions by definition.
+            # The value here is calculated as the minimum of a) the minimum
+            # Python version required, and b) the stable ABI version target.
+            # NB: This needs to be kept in sync with [project.requires-python]
+            # in pyproject.toml.
+            python_version = "3.12"
+        else:
+            python_version = "{0}.{1}".format(*sys.version_info[:2])
 
         bazel_argv = [
             "bazel",