Skip to content

Commit

Permalink
fix: tighten AST to call only, add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
henryiii committed Jan 31, 2021
1 parent bee2eb6 commit 11ee70e
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 55 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Options
| **Build selection** | [`CIBW_PLATFORM`](https://cibuildwheel.readthedocs.io/en/stable/options/#platform) | Override the auto-detected target platform |
| | [`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 |
| | [`CIBW_ARCHS_LINUX`](https://cibuildwheel.readthedocs.io/en/stable/options/#archs) | Build non-native architectures |
| | [`CIBW_PROJECT_REQUIRES_PYTHON`](https://cibuildwheel.readthedocs.io/en/stable/options/#requires-python) | Limit the Python versions to build |
| | [`CIBW_PROJECT_REQUIRES_PYTHON`](https://cibuildwheel.readthedocs.io/en/stable/options/#requires-python) | Manually set the Python compatibility of your project |
| **Build customization** | [`CIBW_ENVIRONMENT`](https://cibuildwheel.readthedocs.io/en/stable/options/#environment) | Set environment variables needed during the build |
| | [`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. |
| | [`CIBW_BEFORE_BUILD`](https://cibuildwheel.readthedocs.io/en/stable/options/#before-build) | Execute a shell command preparing each wheel's build |
Expand All @@ -117,6 +117,7 @@ Options
| | [`CIBW_TEST_SKIP`](https://cibuildwheel.readthedocs.io/en/stable/options/#test-skip) | Skip running tests on some builds |
| **Other** | [`CIBW_BUILD_VERBOSITY`](https://cibuildwheel.readthedocs.io/en/stable/options/#build-verbosity) | Increase/decrease the output of pip wheel |


Working examples
----------------

Expand Down
9 changes: 4 additions & 5 deletions cibuildwheel/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,11 @@ def main() -> None:
test_extras = get_option_from_environment('CIBW_TEST_EXTRAS', platform=platform, default='')
build_verbosity_str = get_option_from_environment('CIBW_BUILD_VERBOSITY', platform=platform, default='')

setup_py = package_dir / 'setup.py'
setup_cfg = package_dir / 'setup.cfg'
pyproject_toml = package_dir / 'pyproject.toml'
package_files = {'setup.py', 'setup.cfg', 'pyproject.toml'}

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

# Passing this in as an environment variable will override pyproject.toml, setup.cfg, or setup.py
Expand Down
37 changes: 17 additions & 20 deletions cibuildwheel/projectfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
from configparser import ConfigParser
from pathlib import Path
from typing import Any, Dict, Optional
from typing import Any, Optional

import toml

Expand All @@ -11,6 +11,8 @@

def get_constant(x: ast.Str) -> str:
return x.s


else:
Constant = ast.Constant

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

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

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


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

setup_py = package_dir / 'setup.py'
setup_cfg = package_dir / 'setup.cfg'
pyproject_toml = package_dir / 'pyproject.toml'

# Read in from pyproject.toml:project.requires-python
try:
info = toml.load(pyproject_toml)
info = toml.load(package_dir / 'pyproject.toml')
return str(info['project']['requires-python'])
except (FileNotFoundError, KeyError, IndexError, TypeError):
pass

# Read in from setup.cfg:options.python_requires
try:
config = ConfigParser()
config.read(setup_cfg)
config.read(package_dir / 'setup.cfg')
return str(config['options']['python_requires'])
except (FileNotFoundError, KeyError, IndexError, TypeError):
pass

try:
with open(setup_py) as f:
with open(package_dir / 'setup.py') as f:
return setup_py_python_requires(f.read())
except FileNotFoundError:
pass
Expand Down
55 changes: 28 additions & 27 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,48 +213,49 @@ This option can also be set using the [command-line option](#command-line) `--ar

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

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

Default: reads your package's Python compatibility from pyproject.toml
(`project.requires-python`) or setup.cfg (`options.python_requires`). If not
found, cibuildwheel assumes the package is compatible with all versions of
Python that it can build.

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


!!! note
Rather than using this option, it's recommended you set
`project.requires-python` in `pyproject.toml` instead - that way, `pip`
knows which wheels are compatible when installing.

`project.requires-python` in `pyproject.toml` instead:
Example `pyproject.toml`:

~~~toml
[project]
requires-python = ">=3.6"
[project]
requires-python = ">=3.6"

# Aside - in pyproject.toml you should always specify minimal build
# system options, like this:

[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"

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

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

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

~~~ini
[options]
python_requires = ">=3.6"
~~~
[options]
python_requires = ">=3.6"

#### Examples

Expand Down
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,15 @@ dev =
mypy
pip-tools
pygithub
ghapi
pymdown-extensions
pytest>=4
pytest-timeout
pyyaml
requests
typing-extensions
rich>=9.6
mypy>=0.800

[options.packages.find]
include =
Expand Down
4 changes: 2 additions & 2 deletions unit_test/projectfiles_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def test_read_setup_py_assign(tmp_path):
)
"""))

assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) == "3.21"
assert get_requires_python_str(tmp_path) == "3.21"
assert setup_py_python_requires(tmp_path.joinpath("setup.py").read_text()) is None
assert get_requires_python_str(tmp_path) is None


def test_read_setup_py_None(tmp_path):
Expand Down

0 comments on commit 11ee70e

Please sign in to comment.