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

Work around for side effects in setup.py script #1257

Closed
wants to merge 10 commits into from
134 changes: 128 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,132 @@ def run_tests(self):
sys.exit(errno)


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',
'--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',
)
if all((arg in no_setup_requires_arguments) or
all(('-' + char) in no_setup_requires_arguments for char in arg[1:])
for arg in argv[1:]):
return {
"cmdclass": {
"build": DummyCFFIBuild,
"install": DummyCFFIInstall,
"test": DummyPyTest,
}
}
else:
return {
"setup_requires": requirements,
"cmdclass": {
"build": CFFIBuild,
"install": CFFIInstall,
"test": PyTest,
}
}


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


class DummyCFFIBuild(CFFIBuild):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this subclass CFFIBuild and then not use it's finalize_options? Seems like this should just subclass build and then not have a finalize_options method? Same for the other classes.

"""
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 finalize_options(self):
build.finalize_options(self)

def run(self):
raise RuntimeError(setup_requires_error)


class DummyCFFIInstall(CFFIInstall):
"""
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 finalize_options(self):
install.finalize_options(self)

def run(self):
raise RuntimeError(setup_requires_error)


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

def finalize_options(self):
test.finalize_options(self)

def run_tests(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 @@ -163,15 +289,11 @@ def run_tests(self):
packages=find_packages(exclude=["tests", "tests.*"]),

install_requires=requirements,
setup_requires=requirements,
tests_require=test_requirements,

# for cffi
zip_safe=False,
ext_package="cryptography",
cmdclass={
"build": CFFIBuild,
"install": CFFIInstall,
"test": PyTest,
}

**keywords_with_side_effects(sys.argv)
)