Repository: pypa/cibuildwheel

Issue:
No build identifiers selected, even though build_config is `cp37-*`
### Description

I'm having an issue building Python 3.7 wheels on Linux, OSX and Windows.  This issue seems to have come very recently and did not occur a week ago in a release test run.

```
cibuildwheel version 2.3.1

Build options:
  platform: 'macos'
  architectures: {<Architecture.x86_64: 'x86_64'>, <Architecture.arm64: 'arm64'>}
  build_selector: BuildSelector(build_config='cp37-*', skip_config='pp* *-musllinux_* cp310-win32')
  output_dir: PosixPath('wheelhouse')
  package_dir: PosixPath('statsmodels')
  test_selector: TestSelector(build_config='*', skip_config='*-*linux_{aarch64,ppc64le,s390x}')
  before_all: ''
  before_build: 'git submodule foreach git checkout  v0.13.2'
  before_test: ''
  build_frontend: 'pip'
  build_verbosity: 2
  dependency_constraints: DependencyConstraints(PosixPath('/Users/runner/hostedtoolcache/Python/3.10.2/x64/lib/python3.10/site-packages/cibuildwheel/resources/constraints.txt'))
  environment: ParsedEnvironment([])
  manylinux_images: None
  musllinux_images: None
  repair_command: 'delocate-listdeps {wheel} && delocate-wheel --require-archs {delocate_archs} -w {dest_dir} {wheel}'
  test_command: 'python -c "import statsmodels; statsmodels.test([\'--skip-examples\',\'--skip-slow\',\'-n\',\'2\'])"'
  test_extras: ''
  test_requires: ['pytest', 'pytest-xdist']

Here we go!

cibuildwheel: No build identifiers selected: BuildSelector(build_config='cp37-*', skip_config='pp* *-musllinux_* cp310-win32')
Error: Process completed with exit code 3.
```

### Build log

https://github.com/MacPython/statsmodels-wheels/runs/5114202151?check_suite_focus=true

### CI config

https://github.com/MacPython/statsmodels-wheels/blob/rel-0.13.0/.github/workflows/build-wheels.yml


Discussion:
This one has me stumped, it's fine locally:

```console
$ CIBW_BUILD='cp37-*' CIBW_SKIP='pp* *-musllinux_* cp310-win32' CIBW_ARCHS_LINUX='x86_64 aarch64' pipx run cibuildwheel --platform linux --print-build-identifiers
cp37-manylinux_x86_64
cp37-manylinux_aarch64
```

I'd recommend statsmodels include `build-backend` (they are using `__legacy__` by default), and `oldest-supported-numpy` instead of listing the NumPy's manually (they are wrong for unusual platforms, and not forward-compatible when a new platform or Python version comes out), but there's not even anything in there that would affect cibuildwheel.
Ahh, no, I understand it. It's Python-requires, that's killing the build. Upstream has dropped Python 3.7 and cibuildwheel knows that, so it's not able to build what you are asking for.

Though to me it looks like this is the old branch where that hasn't happened...
@henryiii We are building an old branch: https://github.com/statsmodels/statsmodels/tree/maintenance/0.13.x

The relvant part of the setup is 

```python
    extras_require=EXTRAS_REQUIRE,
    zip_safe=False,
    python_requires=">=3.7",
)
```

So no issue with python requires. 
> I'd recommend statsmodels include build-backend (they are using __legacy__ by default), and oldest-supported-numpy instead of listing the NumPy's manually (they are wrong for unusual platforms, and not forward-compatible when a new platform or Python version comes out), but there's not even anything in there that would affect cibuildwheel.

Does setting `build-backend` require a new release?  We are already on oldest-supported-numpy 

https://github.com/statsmodels/statsmodels/blob/maintenance/0.13.x/pyproject.toml



No, it’s just nice.

Don’t know why this is doing this, will have to investigate further.
(Also posted on the debugging PR in statsmodels-wheels)

Yep, running this:

```console
$ git checkout rel-0.13.0
$ git submodule update --init --recursive
$ pipx run cibuildwheel --platform linux --print-build-identifiers statsmodels/
cp38-manylinux_x86_64
cp39-manylinux_x86_64
cp310-manylinux_x86_64
cp38-manylinux_i686
cp39-manylinux_i686
cp310-manylinux_i686
pp38-manylinux_x86_64
pp38-manylinux_i686
cp38-musllinux_x86_64
cp39-musllinux_x86_64
cp310-musllinux_x86_64
cp38-musllinux_i686
cp39-musllinux_i686
cp310-musllinux_i686
```

And looking in setup.py, I see this:

```python
  python_requires=">=3.8",
```

I think you have the wrong commit in the submodule.

I wonder if we can make the Python requires setting a bit more obvious.

Also, I'd personally make a cibuildwheel.toml file, put all configuration in that, then use `--config-file=cibuildwheel.toml`, would make local runs easier.

Resolving patch:
diff --git a/cibuildwheel/util.py b/cibuildwheel/util.py
--- a/cibuildwheel/util.py
+++ b/cibuildwheel/util.py
@@ -1,4 +1,5 @@
 import contextlib
+import dataclasses
 import fnmatch
 import itertools
 import os
@@ -16,6 +17,7 @@
 from time import sleep
 from typing import (
     Any,
+    ClassVar,
     Dict,
     Iterable,
     Iterator,
@@ -205,6 +207,8 @@ def selector_matches(patterns: str, string: str) -> bool:
     return any(fnmatch.fnmatch(string, pat) for pat in expanded_patterns)
 
 
+# Once we require Python 3.10+, we can add kw_only=True
+@dataclasses.dataclass
 class IdentifierSelector:
     """
     This class holds a set of build/skip patterns. You call an instance with a
@@ -215,20 +219,12 @@ class IdentifierSelector:
     """
 
     # a pattern that skips prerelease versions, when include_prereleases is False.
-    PRERELEASE_SKIP = ""
-
-    def __init__(
-        self,
-        *,
-        build_config: str,
-        skip_config: str,
-        requires_python: Optional[SpecifierSet] = None,
-        prerelease_pythons: bool = False,
-    ):
-        self.build_config = build_config
-        self.skip_config = skip_config
-        self.requires_python = requires_python
-        self.prerelease_pythons = prerelease_pythons
+    PRERELEASE_SKIP: ClassVar[str] = ""
+
+    skip_config: str
+    build_config: str
+    requires_python: Optional[SpecifierSet] = None
+    prerelease_pythons: bool = False
 
     def __call__(self, build_id: str) -> bool:
         # Filter build selectors by python_requires if set
@@ -241,9 +237,7 @@ def __call__(self, build_id: str) -> bool:
                 return False
 
         # filter out the prerelease pythons if self.prerelease_pythons is False
-        if not self.prerelease_pythons and selector_matches(
-            BuildSelector.PRERELEASE_SKIP, build_id
-        ):
+        if not self.prerelease_pythons and selector_matches(self.PRERELEASE_SKIP, build_id):
             return False
 
         should_build = selector_matches(self.build_config, build_id)
@@ -251,28 +245,17 @@ def __call__(self, build_id: str) -> bool:
 
         return should_build and not should_skip
 
-    def __repr__(self) -> str:
-        result = f"{self.__class__.__name__}(build_config={self.build_config!r}"
-
-        if self.skip_config:
-            result += f", skip_config={self.skip_config!r}"
-        if self.prerelease_pythons:
-            result += ", prerelease_pythons=True"
-
-        result += ")"
-
-        return result
-
 
+@dataclasses.dataclass
 class BuildSelector(IdentifierSelector):
     pass
 
 
 # Note that requires-python is not needed for TestSelector, as you can't test
 # what you can't build.
+@dataclasses.dataclass
 class TestSelector(IdentifierSelector):
-    def __init__(self, *, skip_config: str):
-        super().__init__(build_config="*", skip_config=skip_config)
+    build_config: str = "*"
 
 
 # Taken from https://stackoverflow.com/a/107717


Test patch: