Merge pull request #1367 from alex/line-length

Wrap lines appropriately
diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst
index 3212868..8d046da 100644
--- a/docs/development/test-vectors.rst
+++ b/docs/development/test-vectors.rst
@@ -29,8 +29,8 @@
 * OpenSSL PEM DSA serialization vectors from the `GnuTLS example keys`_.
 * PKCS #8 PEM serialization vectors from
 
-  * GnuTLS: `enc-rsa-pkcs8.pem`_, `enc2-rsa-pkcs8.pem`_, `unenc-rsa-pkcs8.pem`_,
-    `pkcs12_s2k_pem.c`_.
+  * GnuTLS: `enc-rsa-pkcs8.pem`_, `enc2-rsa-pkcs8.pem`_,
+    `unenc-rsa-pkcs8.pem`_, `pkcs12_s2k_pem.c`_.
   * `Botan's ECC private keys`_.
 
 Hashes
diff --git a/setup.py b/setup.py
index 347dbe8..ca72927 100644
--- a/setup.py
+++ b/setup.py
@@ -140,6 +140,151 @@
         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": 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(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)
+
+
 with open(os.path.join(base_dir, "README.rst")) as f:
     long_description = f.read()
 
@@ -182,19 +327,13 @@
     include_package_data=True,
 
     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,
-    },
-
     entry_points={
         "cryptography.backends": backends,
-    }
+    },
+    **keywords_with_side_effects(sys.argv)
 )
diff --git a/tox.ini b/tox.ini
index 7d64be8..d47beac 100644
--- a/tox.ini
+++ b/tox.ini
@@ -27,7 +27,7 @@
     sphinx-build -W -b latex -d {envtmpdir}/doctrees docs docs/_build/latex
     sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
     sphinx-build -W -b spelling docs docs/_build/html
-    doc8 --allow-long-titles README.rst CHANGELOG.rst docs/
+    doc8 --allow-long-titles README.rst CHANGELOG.rst docs/ --ignore-path docs/_build/
 
 [testenv:docs-linkcheck]
 deps =