Repository: pypa/cibuildwheel

Issue:
Create a `CIBW_ALLOW_EMPTY` environment variable for `--allow-empty`
### Description

Would it be possible to enable the CLI flag `--allow-empty` via an environment variable (e.g., `CIBW_ALLOW_EMPTY`)?

Here's our use case:
- We are running a matrix of build jobs in GitHub Actions.
- One of these jobs is `cp313-manylinux_x86_64`.
- Nightly builds will set `CIBW_PRERELEASE_PYTHONS=1` but otherwise this job will not match any wheels.
- We'd like to set the `--allow-empty` flag, but it currently doesn't seem possible through the cibuildwheel GitHub Action.
- (More details: https://github.com/OpenAstronomy/github-actions-workflows/issues/212)

I'm thinking we could compute its value similarly to how `CIBW_PRERELEASE_PYTHONS` is handled:
https://github.com/pypa/cibuildwheel/blob/a4d24056efa85210272c30da9eb1a88add065508/cibuildwheel/options.py#L523-L525

This could be integrated within `options.compute_options` and added to `options.GlobalOptions`:
https://github.com/pypa/cibuildwheel/blob/a4d24056efa85210272c30da9eb1a88add065508/cibuildwheel/options.py#L71-L77

Alternatively, `CIBW_ALLOW_EMPTY` could be checked directly within `__main__.py`:
https://github.com/pypa/cibuildwheel/blob/a4d24056efa85210272c30da9eb1a88add065508/cibuildwheel/__main__.py#L337-L338

I'm happy to open a PR if you think a new environment variable would be appropriate for this option. Thanks!

### Build log

_No response_

### CI config

_No response_

Discussion:

Patch:
diff --git a/cibuildwheel/__main__.py b/cibuildwheel/__main__.py
index a409d725..6c04e4c9 100644
--- a/cibuildwheel/__main__.py
+++ b/cibuildwheel/__main__.py
@@ -334,7 +334,7 @@ def build_in_directory(args: CommandLineArguments) -> None:
 
     if not identifiers:
         message = f"No build identifiers selected: {options.globals.build_selector}"
-        if args.allow_empty:
+        if options.globals.allow_empty:
             print(f"cibuildwheel: {message}", file=sys.stderr)
         else:
             raise errors.NothingToDoError(message)
diff --git a/cibuildwheel/options.py b/cibuildwheel/options.py
index 62094e94..cb360679 100644
--- a/cibuildwheel/options.py
+++ b/cibuildwheel/options.py
@@ -75,6 +75,7 @@ class GlobalOptions:
     build_selector: BuildSelector
     test_selector: TestSelector
     architectures: set[Architecture]
+    allow_empty: bool
 
 
 @dataclasses.dataclass(frozen=True)
@@ -520,6 +521,8 @@ def globals(self) -> GlobalOptions:
             self.reader.get("free-threaded-support", env_plat=False, ignore_empty=True)
         )
 
+        allow_empty = args.allow_empty or strtobool(self.env.get("CIBW_ALLOW_EMPTY", "0"))
+
         prerelease_pythons = args.prerelease_pythons or strtobool(
             self.env.get("CIBW_PRERELEASE_PYTHONS", "0")
         )
@@ -557,6 +560,7 @@ def globals(self) -> GlobalOptions:
             build_selector=build_selector,
             test_selector=test_selector,
             architectures=architectures,
+            allow_empty=allow_empty,
         )
 
     def build_options(self, identifier: str | None) -> BuildOptions:
diff --git a/cibuildwheel/resources/build-platforms.toml b/cibuildwheel/resources/build-platforms.toml
index b75fe878..1403a98a 100644
--- a/cibuildwheel/resources/build-platforms.toml
+++ b/cibuildwheel/resources/build-platforms.toml
@@ -123,12 +123,12 @@ python_configurations = [
   { identifier = "cp312-macosx_x86_64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.4/python-3.12.4-macos11.pkg" },
   { identifier = "cp312-macosx_arm64", version = "3.12", url = "https://www.python.org/ftp/python/3.12.4/python-3.12.4-macos11.pkg" },
   { identifier = "cp312-macosx_universal2", version = "3.12", url = "https://www.python.org/ftp/python/3.12.4/python-3.12.4-macos11.pkg" },
-  { identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b3-macos11.pkg" },
-  { identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b3-macos11.pkg" },
-  { identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b3-macos11.pkg" },
-  { identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b3-macos11.pkg" },
-  { identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b3-macos11.pkg" },
-  { identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b3-macos11.pkg" },
+  { identifier = "cp313-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b4-macos11.pkg" },
+  { identifier = "cp313-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b4-macos11.pkg" },
+  { identifier = "cp313-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b4-macos11.pkg" },
+  { identifier = "cp313t-macosx_x86_64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b4-macos11.pkg" },
+  { identifier = "cp313t-macosx_arm64", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b4-macos11.pkg" },
+  { identifier = "cp313t-macosx_universal2", version = "3.13", url = "https://www.python.org/ftp/python/3.13.0/python-3.13.0b4-macos11.pkg" },
   { identifier = "pp37-macosx_x86_64", version = "3.7", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-osx64.tar.bz2" },
   { identifier = "pp38-macosx_x86_64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_x86_64.tar.bz2" },
   { identifier = "pp38-macosx_arm64", version = "3.8", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-macos_arm64.tar.bz2" },
@@ -154,16 +154,16 @@ python_configurations = [
   { identifier = "cp311-win_amd64", version = "3.11.9", arch = "64" },
   { identifier = "cp312-win32", version = "3.12.4", arch = "32" },
   { identifier = "cp312-win_amd64", version = "3.12.4", arch = "64" },
-  { identifier = "cp313-win32", version = "3.13.0-b3", arch = "32" },
-  { identifier = "cp313t-win32", version = "3.13.0-b3", arch = "32" },
-  { identifier = "cp313-win_amd64", version = "3.13.0-b3", arch = "64" },
-  { identifier = "cp313t-win_amd64", version = "3.13.0-b3", arch = "64" },
+  { identifier = "cp313-win32", version = "3.13.0-b4", arch = "32" },
+  { identifier = "cp313t-win32", version = "3.13.0-b4", arch = "32" },
+  { identifier = "cp313-win_amd64", version = "3.13.0-b4", arch = "64" },
+  { identifier = "cp313t-win_amd64", version = "3.13.0-b4", arch = "64" },
   { identifier = "cp39-win_arm64", version = "3.9.10", arch = "ARM64" },
   { identifier = "cp310-win_arm64", version = "3.10.11", arch = "ARM64" },
   { identifier = "cp311-win_arm64", version = "3.11.9", arch = "ARM64" },
   { identifier = "cp312-win_arm64", version = "3.12.4", arch = "ARM64" },
-  { identifier = "cp313-win_arm64", version = "3.13.0-b3", arch = "ARM64" },
-  { identifier = "cp313t-win_arm64", version = "3.13.0-b3", arch = "ARM64" },
+  { identifier = "cp313-win_arm64", version = "3.13.0-b4", arch = "ARM64" },
+  { identifier = "cp313t-win_arm64", version = "3.13.0-b4", arch = "ARM64" },
   { identifier = "pp37-win_amd64", version = "3.7", arch = "64", url = "https://downloads.python.org/pypy/pypy3.7-v7.3.9-win64.zip" },
   { identifier = "pp38-win_amd64", version = "3.8", arch = "64", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" },
   { identifier = "pp39-win_amd64", version = "3.9", arch = "64", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" },
diff --git a/cibuildwheel/resources/constraints-pyodide312.txt b/cibuildwheel/resources/constraints-pyodide312.txt
index 44339b75..b6306823 100644
--- a/cibuildwheel/resources/constraints-pyodide312.txt
+++ b/cibuildwheel/resources/constraints-pyodide312.txt
@@ -100,14 +100,14 @@ typer==0.12.3
     #   auditwheel-emscripten
     #   pyodide-build
     #   pyodide-cli
-types-requests==2.32.0.20240622
+types-requests==2.32.0.20240712
     # via pyodide-build
 typing-extensions==4.12.2
     # via
     #   pydantic
     #   pydantic-core
     #   typer
-unearth==0.16.0
+unearth==0.16.1
     # via pyodide-build
 urllib3==2.2.2
     # via
diff --git a/cibuildwheel/resources/nodejs.toml b/cibuildwheel/resources/nodejs.toml
index 5424eabe..ef4e195a 100644
--- a/cibuildwheel/resources/nodejs.toml
+++ b/cibuildwheel/resources/nodejs.toml
@@ -1,2 +1,2 @@
 url = "https://nodejs.org/dist/"
-v20 = "v20.15.0"
+v20 = "v20.15.1"
diff --git a/cibuildwheel/resources/pinned_docker_images.cfg b/cibuildwheel/resources/pinned_docker_images.cfg
index 0dca6b19..21527ab5 100644
--- a/cibuildwheel/resources/pinned_docker_images.cfg
+++ b/cibuildwheel/resources/pinned_docker_images.cfg
@@ -1,54 +1,54 @@
 [x86_64]
 manylinux1 = quay.io/pypa/manylinux1_x86_64:2024-04-29-76807b8
 manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177
-manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-12-26-0d38463
-manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.07.07-1
-musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2024.07.07-1
-musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2024.07.07-1
+manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.07.20-1
+musllinux_1_1 = quay.io/pypa/musllinux_1_1_x86_64:2024.07.20-1
+musllinux_1_2 = quay.io/pypa/musllinux_1_2_x86_64:2024.07.20-1
 
 [i686]
 manylinux1 = quay.io/pypa/manylinux1_i686:2024-04-29-76807b8
 manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177
-manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463
-musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2024.07.07-1
-musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2024.07.07-1
+musllinux_1_1 = quay.io/pypa/musllinux_1_1_i686:2024.07.20-1
+musllinux_1_2 = quay.io/pypa/musllinux_1_2_i686:2024.07.20-1
 
 [pypy_x86_64]
 manylinux2010 = quay.io/pypa/manylinux2010_x86_64:2022-08-05-4535177
-manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_x86_64:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_x86_64:2022-12-26-0d38463
-manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.07.07-1
+manylinux_2_28 = quay.io/pypa/manylinux_2_28_x86_64:2024.07.20-1
 
 [pypy_i686]
 manylinux2010 = quay.io/pypa/manylinux2010_i686:2022-08-05-4535177
-manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_i686:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_i686:2022-12-26-0d38463
 
 [aarch64]
-manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463
-manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.07.07-1
-musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2024.07.07-1
-musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2024.07.07-1
+manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.07.20-1
+musllinux_1_1 = quay.io/pypa/musllinux_1_1_aarch64:2024.07.20-1
+musllinux_1_2 = quay.io/pypa/musllinux_1_2_aarch64:2024.07.20-1
 
 [ppc64le]
-manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_ppc64le:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_ppc64le:2022-12-26-0d38463
-manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2024.07.07-1
-musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2024.07.07-1
-musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2024.07.07-1
+manylinux_2_28 = quay.io/pypa/manylinux_2_28_ppc64le:2024.07.20-1
+musllinux_1_1 = quay.io/pypa/musllinux_1_1_ppc64le:2024.07.20-1
+musllinux_1_2 = quay.io/pypa/musllinux_1_2_ppc64le:2024.07.20-1
 
 [s390x]
-manylinux2014 = quay.io/pypa/manylinux2014_s390x:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_s390x:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_s390x:2022-12-26-0d38463
-manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2024.07.07-1
-musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2024.07.07-1
-musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2024.07.07-1
+manylinux_2_28 = quay.io/pypa/manylinux_2_28_s390x:2024.07.20-1
+musllinux_1_1 = quay.io/pypa/musllinux_1_1_s390x:2024.07.20-1
+musllinux_1_2 = quay.io/pypa/musllinux_1_2_s390x:2024.07.20-1
 
 [pypy_aarch64]
-manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.07.07-1
+manylinux2014 = quay.io/pypa/manylinux2014_aarch64:2024.07.20-1
 manylinux_2_24 = quay.io/pypa/manylinux_2_24_aarch64:2022-12-26-0d38463
-manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.07.07-1
+manylinux_2_28 = quay.io/pypa/manylinux_2_28_aarch64:2024.07.20-1
 
diff --git a/docs/options.md b/docs/options.md
index 0906d923..5cb23878 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -621,6 +621,28 @@ #### Examples
     CIBW_PRERELEASE_PYTHONS: True
     ```
 
+### `CIBW_ALLOW_EMPTY` {: #allow-empty}
+> Suppress the error code if no wheels match the specified build identifiers
+
+When none of the specified build identifiers match any available versions,
+cibuildwheel will typically return error code 3, indicating that there are
+no wheels to build. Enabling this option will suppress this error, allowing
+the build process to complete without signaling an error.
+
+Default: Off (0). Error code 3 is returned when no builds are selected.
+
+This option can also be set using the [command-line option](#command-line)
+`--allow-empty`. This option is not available in the `pyproject.toml` config.
+
+#### Examples
+
+!!! tab examples "Environment variables"
+
+    ```yaml
+    # Prevent an error code if the build does not match any wheels
+    CIBW_ALLOW_EMPTY: True
+    ```
+
 ## Build customization
 
 ### `CIBW_BUILD_FRONTEND` {: #build-frontend}
@@ -1667,7 +1689,7 @@ ### Return codes
 Specific error codes are defined:
 
 - 2 means a configuration error
-- 3 means no builds are selected (and --allow-empty wasn't passed)
+- 3 means no builds are selected (and [`--allow-empty`](#allow-empty) wasn't passed)
 - 4 means you specified an option that has been deprecated.
 
 
diff --git a/docs/working-examples.md b/docs/working-examples.md
index 2e7c9e4a..d7348969 100644
--- a/docs/working-examples.md
+++ b/docs/working-examples.md
@@ -38,7 +38,6 @@
 | [UltraJSON][]                     | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Ultra fast JSON decoder and encoder written in C with Python bindings |
 | [OpenSpiel][]                     | ![github icon][] | ![apple icon][] ![linux icon][] | OpenSpiel is a collection of environments and algorithms for research in general reinforcement learning and search/planning in games. |
 | [aiortc][]                        | ![github icon][] | ![apple icon][] ![linux icon][] | WebRTC and ORTC implementation for Python using asyncio. |
-| [Confluent client for Kafka][]    | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` |
 | [Dependency Injector][]           | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Dependency injection framework for Python, uses Windows TravisCI |
 | [pyzmq][]                         | ![github icon][] ![circleci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python bindings for zeromq, the networking library. Uses Cython on CPython and CFFI on PyPy. ARM wheels for linux are built natively on CircleCI.  |
 | [Implicit][]                      | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes GPU support for linux wheels |
@@ -62,8 +61,8 @@
 | [ruptures][]                      | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Extensive Cython + NumPy [pyproject.toml](https://github.com/deepcharles/ruptures/blob/master/pyproject.toml) example. |
 | [OpenTimelineIO][]                | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Open Source API and interchange format for editorial timeline information. |
 | [PyTables][]                      | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python package to manage extremely large amounts of data |
-| [Parselmouth][]                   | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python interface to the Praat software package, using pybind11, C++17 and CMake, with the core Praat static library built only once and shared between wheels. |
 | [google neuroglancer][]           | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | WebGL-based viewer for volumetric data |
+| [Parselmouth][]                   | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A Python interface to the Praat software package, using pybind11, C++17 and CMake, with the core Praat static library built only once and shared between wheels. |
 | [DeepForest][]                    | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | An Efficient, Scalable and Optimized Python Framework for Deep Forest (2021.2.1) |
 | [AutoPy][]                        | ![travisci icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Includes a Windows Travis build. |
 | [H3-py][]                         | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Python bindings for H3, a hierarchical hexagonal geospatial indexing system |
@@ -110,12 +109,13 @@
 | [pyjet][]                         | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | The interface between FastJet and NumPy |
 | [ril][]                           | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A python binding to Rust Imaging library using maturin and Pyo3, utilizes Github Action cache to improve speed. Builds abi3 wheels. |
 | [GSD][]                           | ![github icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | Cython and NumPy project with 64-bit wheels. |
+| [Confluent client for Kafka][]    | ![travisci icon][] | ![apple icon][] ![linux icon][] ![windows icon][] | setup in `tools/wheels/build-wheels.bat` |
 | [SiPM][]                          | ![github icon][] | ![apple icon][] ![linux icon][] | High performance library for SiPM detectors simulation using C++17, OpenMP and AVX2 intrinsics. |
 | [aalink][]                        | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Async Python interface for Ableton Link. |
 | [CorrectionLib][]                 | ![github icon][] | ![apple icon][] ![linux icon][] | Structured JSON powered correction library for HEP, designed for the CMS experiment at CERN. |
 | [xmlstarlet][]                    | ![github icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Python 3.6+ CFFI bindings with true MSVC build. |
-| [pyinstrument_cext][]             | ![travisci icon][] ![appveyor icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A simple C extension, without external dependencies |
 | [werpy][]                         | ![github icon][] | ![windows icon][] ![linux icon][] ![apple icon][] | An ultra-fast python package using optimized dynamic programming to compute the Word Error Rate (WER). |
+| [pyinstrument_cext][]             | ![travisci icon][] ![appveyor icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | A simple C extension, without external dependencies |
 | [pybind11 cross build example][]  | ![github icon][] ![gitlab icon][] | ![windows icon][] ![apple icon][] ![linux icon][] | Same as pybind11 cmake_example but used to demo Linux ARM + Windows + macOS builds on GitLab |
 
 [scikit-learn]: https://github.com/scikit-learn/scikit-learn
@@ -148,7 +148,6 @@
 [UltraJSON]: https://github.com/ultrajson/ultrajson
 [OpenSpiel]: https://github.com/google-deepmind/open_spiel
 [aiortc]: https://github.com/aiortc/aiortc
-[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python
 [Dependency Injector]: https://github.com/ets-labs/python-dependency-injector
 [pyzmq]: https://github.com/zeromq/pyzmq
 [Implicit]: https://github.com/benfred/implicit
@@ -172,8 +171,8 @@
 [ruptures]: https://github.com/deepcharles/ruptures
 [OpenTimelineIO]: https://github.com/AcademySoftwareFoundation/OpenTimelineIO
 [PyTables]: https://github.com/PyTables/PyTables
-[Parselmouth]: https://github.com/YannickJadoul/Parselmouth
 [google neuroglancer]: https://github.com/google/neuroglancer
+[Parselmouth]: https://github.com/YannickJadoul/Parselmouth
 [DeepForest]: https://github.com/LAMDA-NJU/Deep-Forest
 [AutoPy]: https://github.com/autopilot-rs/autopy
 [H3-py]: https://github.com/uber/h3-py
@@ -220,12 +219,13 @@
 [pyjet]: https://github.com/scikit-hep/pyjet
 [ril]: https://github.com/Cryptex-github/ril-py
 [GSD]: https://github.com/glotzerlab/gsd
+[Confluent client for Kafka]: https://github.com/confluentinc/confluent-kafka-python
 [SiPM]: https://github.com/EdoPro98/SimSiPM
 [aalink]: https://github.com/artfwo/aalink
 [CorrectionLib]: https://github.com/cms-nanoAOD/correctionlib
 [xmlstarlet]: https://github.com/dimitern/xmlstarlet
-[pyinstrument_cext]: https://github.com/joerick/pyinstrument_cext
 [werpy]: https://github.com/analyticsinmotion/werpy
+[pyinstrument_cext]: https://github.com/joerick/pyinstrument_cext
 [pybind11 cross build example]: https://github.com/wbarnha/pybind_cmake_example_crossbuild
 
 [appveyor icon]: data/readme_icons/appveyor.svg


Test patch:
diff --git a/test/test_0_basic.py b/test/test_0_basic.py
index 0a4a0f04..6663b2da 100644
--- a/test/test_0_basic.py
+++ b/test/test_0_basic.py
@@ -69,7 +69,15 @@ def test_build_identifiers(tmp_path):
     ), f"{expected_wheels} vs {build_identifiers}"
 
 
-def test_allow_empty(tmp_path):
+@pytest.mark.parametrize(
+    ("add_args", "env_allow_empty"),
+    [
+        (["--allow-empty"], {}),
+        (["--allow-empty"], {"CIBW_ALLOW_EMPTY": "0"}),
+        (None, {"CIBW_ALLOW_EMPTY": "1"}),
+    ],
+)
+def test_allow_empty(tmp_path, add_args, env_allow_empty):
     project_dir = tmp_path / "project"
     basic_project.generate(project_dir)
 
@@ -77,8 +85,8 @@ def test_allow_empty(tmp_path):
     # without error
     actual_wheels = utils.cibuildwheel_run(
         project_dir,
-        add_env={"CIBW_BUILD": "BUILD_NOTHING_AT_ALL"},
-        add_args=["--allow-empty"],
+        add_env={"CIBW_BUILD": "BUILD_NOTHING_AT_ALL", **env_allow_empty},
+        add_args=add_args,
     )
 
     # check that nothing was built