Skip to content

Commit 11ee70e

Browse files
committed
fix: tighten AST to call only, add docs
1 parent bee2eb6 commit 11ee70e

File tree

6 files changed

+55
-55
lines changed

6 files changed

+55
-55
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ Options
103103
| **Build selection** | [`CIBW_PLATFORM`](https://cibuildwheel.readthedocs.io/en/stable/options/#platform) | Override the auto-detected target platform |
104104
| | [`CIBW_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) <br> [`CIBW_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip) | Choose the Python versions to build |
105105
| | [`CIBW_ARCHS_LINUX`](https://cibuildwheel.readthedocs.io/en/stable/options/#archs) | Build non-native architectures |
106-
| | [`CIBW_PROJECT_REQUIRES_PYTHON`](https://cibuildwheel.readthedocs.io/en/stable/options/#requires-python) | Limit the Python versions to build |
106+
| | [`CIBW_PROJECT_REQUIRES_PYTHON`](https://cibuildwheel.readthedocs.io/en/stable/options/#requires-python) | Manually set the Python compatibility of your project |
107107
| **Build customization** | [`CIBW_ENVIRONMENT`](https://cibuildwheel.readthedocs.io/en/stable/options/#environment) | Set environment variables needed during the build |
108108
| | [`CIBW_BEFORE_ALL`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-all) | Execute a shell command on the build system before any wheels are built. |
109109
| | [`CIBW_BEFORE_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build |
@@ -117,6 +117,7 @@ Options
117117
| | [`CIBW_TEST_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-skip) | Skip running tests on some builds |
118118
| **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-verbosity) | Increase/decrease the output of pip wheel |
119119

120+
120121
Working examples
121122
----------------
122123

cibuildwheel/__main__.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,11 @@ def main() -> None:
159159
test_extras = get_option_from_environment('CIBW_TEST_EXTRAS', platform=platform, default='')
160160
build_verbosity_str = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')
161161

162-
setup_py = package_dir / 'setup.py'
163-
setup_cfg = package_dir / 'setup.cfg'
164-
pyproject_toml = package_dir / 'pyproject.toml'
162+
package_files = {'setup.py', 'setup.cfg', 'pyproject.toml'}
165163

166-
if not pyproject_toml.exists() and not setup_cfg.exists() and not setup_py.exists():
167-
print('cibuildwheel: Could not find setup.py, setup.cfg or pyproject.toml at root of package', file=sys.stderr)
164+
if not any(package_dir.joinpath(name).exists() for name in package_files):
165+
names = ', '.join(sorted(package_files, reverse=True))
166+
print(f'cibuildwheel: Could not find any of {{{names}}} at root of package', file=sys.stderr)
168167
sys.exit(2)
169168

170169
# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py

cibuildwheel/projectfiles.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
from configparser import ConfigParser
44
from pathlib import Path
5-
from typing import Any, Dict, Optional
5+
from typing import Any, Optional
66

77
import toml
88

@@ -11,6 +11,8 @@
1111

1212
def get_constant(x: ast.Str) -> str:
1313
return x.s
14+
15+
1416
else:
1517
Constant = ast.Constant
1618

@@ -21,24 +23,23 @@ def get_constant(x: ast.Constant) -> Any:
2123
class Analyzer(ast.NodeVisitor):
2224
def __init__(self) -> None:
2325
self.requires_python: Optional[str] = None
24-
self.constants: Dict[str, str] = {}
2526

26-
def visit_Assign(self, node: ast.Assign) -> None:
27-
for target in node.targets:
28-
if (
29-
isinstance(target, ast.Name)
30-
and isinstance(node.value, Constant)
31-
and isinstance(get_constant(node.value), str)
32-
):
33-
self.constants[target.id] = get_constant(node.value)
27+
def visit(self, content: ast.AST) -> None:
28+
for node in ast.walk(content):
29+
for child in ast.iter_child_nodes(node):
30+
child.parent = node # type: ignore
31+
super().visit(content)
3432

3533
def visit_keyword(self, node: ast.keyword) -> None:
3634
self.generic_visit(node)
3735
if node.arg == "python_requires":
38-
if isinstance(node.value, Constant):
36+
# Must not be nested in an if or other structure
37+
# This will be Module -> Expr -> Call -> keyword
38+
if (
39+
not hasattr(node.parent.parent.parent, "parent") # type: ignore
40+
and isinstance(node.value, Constant)
41+
):
3942
self.requires_python = get_constant(node.value)
40-
elif isinstance(node.value, ast.Name):
41-
self.requires_python = self.constants.get(node.value.id)
4243

4344

4445
def setup_py_python_requires(content: str) -> Optional[str]:
@@ -54,27 +55,23 @@ def setup_py_python_requires(content: str) -> Optional[str]:
5455
def get_requires_python_str(package_dir: Path) -> Optional[str]:
5556
"Return the python requires string from the most canonical source available, or None"
5657

57-
setup_py = package_dir / 'setup.py'
58-
setup_cfg = package_dir / 'setup.cfg'
59-
pyproject_toml = package_dir / 'pyproject.toml'
60-
6158
# Read in from pyproject.toml:project.requires-python
6259
try:
63-
info = toml.load(pyproject_toml)
60+
info = toml.load(package_dir / 'pyproject.toml')
6461
return str(info['project']['requires-python'])
6562
except (FileNotFoundError, KeyError, IndexError, TypeError):
6663
pass
6764

6865
# Read in from setup.cfg:options.python_requires
6966
try:
7067
config = ConfigParser()
71-
config.read(setup_cfg)
68+
config.read(package_dir / 'setup.cfg')
7269
return str(config['options']['python_requires'])
7370
except (FileNotFoundError, KeyError, IndexError, TypeError):
7471
pass
7572

7673
try:
77-
with open(setup_py) as f:
74+
with open(package_dir / 'setup.py') as f:
7875
return setup_py_python_requires(f.read())
7976
except FileNotFoundError:
8077
pass

docs/options.md

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -213,48 +213,49 @@ This option can also be set using the [command-line option](#command-line) `--ar
213213

214214
By default, cibuildwheel reads your package's Python compatibility from
215215
`pyproject.toml` following [PEP621](https://www.python.org/dev/peps/pep-0621/)
216-
or from `setup.cfg`. If you need to override this behaviour for some reason,
217-
you can use this option.
216+
or from `setup.cfg`; finally it will try to inspect the AST of `setup.py` for a
217+
simple keyword assignment in a top level function call. If you need to override
218+
this behaviour for some reason, you can use this option.
218219

219220
When setting this option, the syntax is the same as `project.requires-python`,
220221
using 'version specifiers' like `>=3.6`, according to
221222
[PEP440](https://www.python.org/dev/peps/pep-0440/#version-specifiers).
222223

223-
Default: reads your package's Python compatibility from pyproject.toml
224-
(`project.requires-python`) or setup.cfg (`options.python_requires`). If not
225-
found, cibuildwheel assumes the package is compatible with all versions of
226-
Python that it can build.
227-
228-
Platform-specific variants also available:<br/>
229-
`CIBW_PROJECT_REQUIRES_PYTHON_MACOS` | `CIBW_PROJECT_REQUIRES_PYTHON_WINDOWS` | `CIBW_PROJECT_REQUIRES_PYTHON_LINUX`
224+
Default: reads your package's Python compatibility from `pyproject.toml`
225+
(`project.requires-python`) or `setup.cfg` (`options.python_requires`) or
226+
`setup.py` `setup(python_requires="...")`. If not found, cibuildwheel assumes
227+
the package is compatible with all versions of Python that it can build.
230228

231229

232230
!!! note
233231
Rather than using this option, it's recommended you set
234-
`project.requires-python` in `pyproject.toml` instead - that way, `pip`
235-
knows which wheels are compatible when installing.
236-
232+
`project.requires-python` in `pyproject.toml` instead:
237233
Example `pyproject.toml`:
238234

239-
~~~toml
240-
[project]
241-
requires-python = ">=3.6"
235+
[project]
236+
requires-python = ">=3.6"
237+
238+
# Aside - in pyproject.toml you should always specify minimal build
239+
# system options, like this:
240+
241+
[build-system]
242+
requires = ["setuptools>=42", "wheel"]
243+
build-backend = "setuptools.build_meta"
242244

243-
# aside - in pyproject.toml you should always specify minimal build system
244-
# options, like this:
245245

246-
[build-system]
247-
requires = ["setuptools>=42", "wheel"]
248-
build-backend = "setuptools.build_meta"
249-
~~~
246+
Currently, setuptools has not yet added support for reading this value from
247+
pyproject.toml yet, and so does not copy it to Requires-Python in the wheel
248+
metadata. This mechanism is used by `pip` to scan through older versions of
249+
your package until it finds a release compatible with the curernt version
250+
of Python compatible when installing, so it is an important value to set if
251+
you plan to drop support for a version of Python in the future.
250252

251-
If you don't want to use this yet, you can also use the currently
252-
recommended location for setuptools in `setup.cfg`. Example `setup.cfg`:
253+
If you don't want to list this value twice, you can also use the setuptools
254+
specific location in `setup.cfg` and cibuildwheel will detect it from
255+
there. Example `setup.cfg`:
253256

254-
~~~ini
255-
[options]
256-
python_requires = ">=3.6"
257-
~~~
257+
[options]
258+
python_requires = ">=3.6"
258259

259260
#### Examples
260261

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ dev =
4646
mypy
4747
pip-tools
4848
pygithub
49+
ghapi
4950
pymdown-extensions
5051
pytest>=4
5152
pytest-timeout
5253
pyyaml
5354
requests
5455
typing-extensions
5556
rich>=9.6
57+
mypy>=0.800
5658

5759
[options.packages.find]
5860
include =

unit_test/projectfiles_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ def test_read_setup_py_assign(tmp_path):
5454
)
5555
"""))
5656

57-
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "3.21"
58-
assert get_requires_python_str(tmp_path) == "3.21"
57+
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None
58+
assert get_requires_python_str(tmp_path) is None
5959

6060

6161
def test_read_setup_py_None(tmp_path):

0 commit comments

Comments
 (0)