Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove keywords with side effects hack in setup.py #5426

Merged
merged 1 commit into from
Aug 25, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
147 changes: 5 additions & 142 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@
import os
import platform
import sys
from distutils.command.build import build

import pkg_resources

import setuptools
from setuptools import find_packages, setup
from setuptools.command.install import install


if pkg_resources.parse_version(
Expand Down Expand Up @@ -49,145 +47,6 @@
)


def keywords_with_side_effects(argv):
"""
Get a dictionary with setup keywords that (can) have side effects.

:param argv: A list of strings with command line arguments.
:returns: A dictionary with keyword arguments for the ``setup()`` function.

This setup.py script uses the setuptools 'setup_requires' feature because
this is required by the cffi package to compile extension modules. The
purpose of ``keywords_with_side_effects()`` is to avoid triggering the cffi
build process as a result of setup.py invocations that don't need the cffi
module to be built (setup.py serves the dual purpose of exposing package
metadata).

All of the options listed by ``python setup.py --help`` that print
information should be recognized here. The commands ``clean``,
``egg_info``, ``register``, ``sdist`` and ``upload`` are also recognized.
Any combination of these options and commands is also supported.

This function was originally based on the `setup.py script`_ of SciPy (see
also the discussion in `pip issue #25`_).

.. _pip issue #25: https://github.com/pypa/pip/issues/25
.. _setup.py script: https://github.com/scipy/scipy/blob/master/setup.py
"""
no_setup_requires_arguments = (
"-h",
"--help",
"-n",
"--dry-run",
"-q",
"--quiet",
"-v",
"--verbose",
"-V",
"--version",
"--author",
"--author-email",
"--classifiers",
"--contact",
"--contact-email",
"--description",
"--egg-base",
"--fullname",
"--help-commands",
"--keywords",
"--licence",
"--license",
"--long-description",
"--maintainer",
"--maintainer-email",
"--name",
"--no-user-cfg",
"--obsoletes",
"--platforms",
"--provides",
"--requires",
"--url",
"clean",
"egg_info",
"register",
"sdist",
"upload",
)

def is_short_option(argument):
"""Check whether a command line argument is a short option."""
return len(argument) >= 2 and argument[0] == "-" and argument[1] != "-"

def expand_short_options(argument):
"""Expand combined short options into canonical short options."""
return ("-" + char for char in argument[1:])

def argument_without_setup_requirements(argv, i):
"""Check whether a command line argument needs setup requirements."""
if argv[i] in no_setup_requires_arguments:
# Simple case: An argument which is either an option or a command
# which doesn't need setup requirements.
return True
elif is_short_option(argv[i]) and all(
option in no_setup_requires_arguments
for option in expand_short_options(argv[i])
):
# Not so simple case: Combined short options none of which need
# setup requirements.
return True
elif argv[i - 1 : i] == ["--egg-base"]:
# Tricky case: --egg-info takes an argument which should not make
# us use setup_requires (defeating the purpose of this code).
return True
else:
return False

if all(
argument_without_setup_requirements(argv, i)
for i in range(1, len(argv))
):
return {"cmdclass": {"build": DummyBuild, "install": DummyInstall}}
else:
cffi_modules = [
"src/_cffi_src/build_openssl.py:ffi",
"src/_cffi_src/build_padding.py:ffi",
]

return {
"setup_requires": setup_requirements,
"cffi_modules": cffi_modules,
}


setup_requires_error = (
"Requested setup command that needs 'setup_requires' "
"while command line arguments implied a side effect "
"free command or option."
)


class DummyBuild(build):
"""
This class makes it very obvious when ``keywords_with_side_effects()`` has
incorrectly interpreted the command line arguments to ``setup.py build`` as
one of the 'side effect free' commands or options.
"""

def run(self):
raise RuntimeError(setup_requires_error)


class DummyInstall(install):
"""
This class makes it very obvious when ``keywords_with_side_effects()`` has
incorrectly interpreted the command line arguments to ``setup.py install``
as one of the 'side effect free' commands or options.
"""

def run(self):
raise RuntimeError(setup_requires_error)


with open(os.path.join(base_dir, "README.rst")) as f:
long_description = f.read()

Expand Down Expand Up @@ -230,6 +89,7 @@ def run(self):
include_package_data=True,
python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*",
install_requires=["six >= 1.4.1"] + setup_requirements,
setup_requires=setup_requirements,
extras_require={
":python_version < '3'": ["enum34", "ipaddress"],
"test": [
Expand Down Expand Up @@ -257,5 +117,8 @@ def run(self):
# for cffi
zip_safe=False,
ext_package="cryptography.hazmat.bindings",
**keywords_with_side_effects(sys.argv)
cffi_modules=[
"src/_cffi_src/build_openssl.py:ffi",
"src/_cffi_src/build_padding.py:ffi",
],
)