summaryrefslogtreecommitdiff
path: root/setup.py
diff options
context:
space:
mode:
authorDonald Stufft <donald@stufft.io>2014-12-05 19:20:12 -0500
committerDonald Stufft <donald@stufft.io>2014-12-05 21:27:44 -0500
commit97a277d5a6ead41ff38ea90e85c01b7fc1af4792 (patch)
treeb5cd081cb6999cf55efac933937c66ccadbb68f0 /setup.py
parentf94682bd265391d29dcae08dfcf5a2964ed0ed0e (diff)
downloadpy-bcrypt-git-97a277d5a6ead41ff38ea90e85c01b7fc1af4792.tar.gz
Port the setup.py from pyca/cryptography to bcrypt
Diffstat (limited to 'setup.py')
-rw-r--r--setup.py210
1 files changed, 185 insertions, 25 deletions
diff --git a/setup.py b/setup.py
index fd63da3..5f73667 100644
--- a/setup.py
+++ b/setup.py
@@ -1,11 +1,13 @@
#!/usr/bin/env python
-import os
import sys
+from distutils.command.build import build
from setuptools import setup
+from setuptools.command.install import install
from setuptools.command.test import test
+CFFI_DEPENDENCY = "cffi>=0.8"
SIX_DEPENDENCY = "six>=1.4.1"
@@ -22,24 +24,42 @@ class _AttrDict(dict):
self[key] = value
-# This is really hacky, but it ensures that if setup_requires are being used
-# setup.py can import them. No idea why this is required.
-sys.path += [x for x in os.listdir(".") if x.endswith(".egg")]
+# Manually extract the __about__
+__about__ = _AttrDict()
+with open("bcrypt/__about__.py") as fp:
+ exec(fp.read(), __about__)
-try:
- from bcrypt import __about__, _ffi
-except ImportError:
- # installing - there is no cffi yet
- ext_modules = []
+def get_ext_modules():
+ from bcrypt import _ffi
+ return [_ffi.verifier.get_extension()]
- # Manually extract the __about__
- __about__ = _AttrDict()
- with open("bcrypt/__about__.py") as fp:
- exec(fp.read(), __about__)
-else:
- # building bdist - cffi is here!
- ext_modules = [_ffi.verifier.get_extension()]
+
+class CFFIBuild(build):
+ """
+ This class exists, instead of just providing ``ext_modules=[...]`` directly
+ in ``setup()`` because importing cryptography requires we have several
+ packages installed first.
+
+ By doing the imports here we ensure that packages listed in
+ ``setup_requires`` are already installed.
+ """
+
+ def finalize_options(self):
+ self.distribution.ext_modules = get_ext_modules()
+ build.finalize_options(self)
+
+
+class CFFIInstall(install):
+ """
+ As a consequence of CFFIBuild and it's late addition of ext_modules, we
+ need the equivalent for the ``install`` command to install into platlib
+ install-dir rather than purelib.
+ """
+
+ def finalize_options(self):
+ self.distribution.ext_modules = get_ext_modules()
+ install.finalize_options(self)
class PyTest(test):
@@ -54,6 +74,151 @@ class PyTest(test):
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',
+ '--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": DummyCFFIBuild,
+ "install": DummyCFFIInstall,
+ "test": DummyPyTest,
+ }
+ }
+ else:
+ return {
+ "setup_requires": [CFFI_DEPENDENCY, SIX_DEPENDENCY],
+ "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(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 DummyCFFIInstall(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)
+
+
+class DummyPyTest(test):
+ """
+ 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 run_tests(self):
+ raise RuntimeError(setup_requires_error)
+
+
setup(
name=__about__.__title__,
version=__about__.__version__,
@@ -66,12 +231,8 @@ setup(
author=__about__.__author__,
author_email=__about__.__email__,
- setup_requires=[
- "cffi",
- SIX_DEPENDENCY,
- ],
install_requires=[
- "cffi",
+ CFFI_DEPENDENCY,
SIX_DEPENDENCY,
],
extras_require={
@@ -93,10 +254,7 @@ setup(
"bcrypt": ["crypt_blowfish-1.2/*"],
},
- ext_modules=ext_modules,
-
zip_safe=False,
- cmdclass={"test": PyTest},
classifiers=[
"Programming Language :: Python :: Implementation :: CPython",
@@ -107,5 +265,7 @@ setup(
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
- ]
+ ],
+
+ **keywords_with_side_effects(sys.argv)
)