1#!/usr/bin/env python 2 3# This file is dual licensed under the terms of the Apache License, Version 4# 2.0, and the BSD License. See the LICENSE file in the root of this repository 5# for complete details. 6 7from __future__ import absolute_import, division, print_function 8 9import os 10import platform 11import subprocess 12import sys 13from distutils.command.build import build 14 15import pkg_resources 16 17import setuptools 18from setuptools import find_packages, setup 19from setuptools.command.install import install 20from setuptools.command.test import test 21 22 23if ( 24 pkg_resources.parse_version(setuptools.__version__) < 25 pkg_resources.parse_version("18.5") 26): 27 raise RuntimeError( 28 "cryptography requires setuptools 18.5 or newer, please upgrade to a " 29 "newer version of setuptools" 30 ) 31 32base_dir = os.path.dirname(__file__) 33src_dir = os.path.join(base_dir, "src") 34 35# When executing the setup.py, we need to be able to import ourselves, this 36# means that we need to add the src/ directory to the sys.path. 37sys.path.insert(0, src_dir) 38 39about = {} 40with open(os.path.join(src_dir, "cryptography", "__about__.py")) as f: 41 exec(f.read(), about) 42 43 44VECTORS_DEPENDENCY = "cryptography_vectors=={0}".format(about['__version__']) 45 46# `setup_requirements` must be kept in sync with `pyproject.toml` 47setup_requirements = ["cffi>=1.8,!=1.11.3"] 48 49if platform.python_implementation() == "PyPy": 50 if sys.pypy_version_info < (5, 4): 51 raise RuntimeError( 52 "cryptography is not compatible with PyPy < 5.4. Please upgrade " 53 "PyPy to use this library." 54 ) 55 56test_requirements = [ 57 "pytest>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2", 58 "pretend", 59 "iso8601", 60 "pytz", 61 "hypothesis>=1.11.4,!=3.79.2", 62] 63 64 65# If there's no vectors locally that probably means we are in a tarball and 66# need to go and get the matching vectors package from PyPi 67if not os.path.exists(os.path.join(base_dir, "vectors/setup.py")): 68 test_requirements.append(VECTORS_DEPENDENCY) 69 70 71class PyTest(test): 72 def finalize_options(self): 73 test.finalize_options(self) 74 self.test_args = [] 75 self.test_suite = True 76 77 # This means there's a vectors/ folder with the package in here. 78 # cd into it, install the vectors package and then refresh sys.path 79 if VECTORS_DEPENDENCY not in test_requirements: 80 subprocess.check_call( 81 [sys.executable, "setup.py", "install"], cwd="vectors" 82 ) 83 pkg_resources.get_distribution("cryptography_vectors").activate() 84 85 def run_tests(self): 86 # Import here because in module scope the eggs are not loaded. 87 import pytest 88 test_args = [os.path.join(base_dir, "tests")] 89 errno = pytest.main(test_args) 90 sys.exit(errno) 91 92 93def keywords_with_side_effects(argv): 94 """ 95 Get a dictionary with setup keywords that (can) have side effects. 96 97 :param argv: A list of strings with command line arguments. 98 :returns: A dictionary with keyword arguments for the ``setup()`` function. 99 100 This setup.py script uses the setuptools 'setup_requires' feature because 101 this is required by the cffi package to compile extension modules. The 102 purpose of ``keywords_with_side_effects()`` is to avoid triggering the cffi 103 build process as a result of setup.py invocations that don't need the cffi 104 module to be built (setup.py serves the dual purpose of exposing package 105 metadata). 106 107 All of the options listed by ``python setup.py --help`` that print 108 information should be recognized here. The commands ``clean``, 109 ``egg_info``, ``register``, ``sdist`` and ``upload`` are also recognized. 110 Any combination of these options and commands is also supported. 111 112 This function was originally based on the `setup.py script`_ of SciPy (see 113 also the discussion in `pip issue #25`_). 114 115 .. _pip issue #25: https://github.com/pypa/pip/issues/25 116 .. _setup.py script: https://github.com/scipy/scipy/blob/master/setup.py 117 """ 118 no_setup_requires_arguments = ( 119 '-h', '--help', 120 '-n', '--dry-run', 121 '-q', '--quiet', 122 '-v', '--verbose', 123 '-V', '--version', 124 '--author', 125 '--author-email', 126 '--classifiers', 127 '--contact', 128 '--contact-email', 129 '--description', 130 '--egg-base', 131 '--fullname', 132 '--help-commands', 133 '--keywords', 134 '--licence', 135 '--license', 136 '--long-description', 137 '--maintainer', 138 '--maintainer-email', 139 '--name', 140 '--no-user-cfg', 141 '--obsoletes', 142 '--platforms', 143 '--provides', 144 '--requires', 145 '--url', 146 'clean', 147 'egg_info', 148 'register', 149 'sdist', 150 'upload', 151 ) 152 153 def is_short_option(argument): 154 """Check whether a command line argument is a short option.""" 155 return len(argument) >= 2 and argument[0] == '-' and argument[1] != '-' 156 157 def expand_short_options(argument): 158 """Expand combined short options into canonical short options.""" 159 return ('-' + char for char in argument[1:]) 160 161 def argument_without_setup_requirements(argv, i): 162 """Check whether a command line argument needs setup requirements.""" 163 if argv[i] in no_setup_requires_arguments: 164 # Simple case: An argument which is either an option or a command 165 # which doesn't need setup requirements. 166 return True 167 elif (is_short_option(argv[i]) and 168 all(option in no_setup_requires_arguments 169 for option in expand_short_options(argv[i]))): 170 # Not so simple case: Combined short options none of which need 171 # setup requirements. 172 return True 173 elif argv[i - 1:i] == ['--egg-base']: 174 # Tricky case: --egg-info takes an argument which should not make 175 # us use setup_requires (defeating the purpose of this code). 176 return True 177 else: 178 return False 179 180 if all(argument_without_setup_requirements(argv, i) 181 for i in range(1, len(argv))): 182 return { 183 "cmdclass": { 184 "build": DummyBuild, 185 "install": DummyInstall, 186 "test": DummyPyTest, 187 } 188 } 189 else: 190 cffi_modules = [ 191 "src/_cffi_src/build_openssl.py:ffi", 192 "src/_cffi_src/build_constant_time.py:ffi", 193 "src/_cffi_src/build_padding.py:ffi", 194 ] 195 196 return { 197 "setup_requires": setup_requirements, 198 "cmdclass": { 199 "test": PyTest, 200 }, 201 "cffi_modules": cffi_modules 202 } 203 204 205setup_requires_error = ("Requested setup command that needs 'setup_requires' " 206 "while command line arguments implied a side effect " 207 "free command or option.") 208 209 210class DummyBuild(build): 211 """ 212 This class makes it very obvious when ``keywords_with_side_effects()`` has 213 incorrectly interpreted the command line arguments to ``setup.py build`` as 214 one of the 'side effect free' commands or options. 215 """ 216 217 def run(self): 218 raise RuntimeError(setup_requires_error) 219 220 221class DummyInstall(install): 222 """ 223 This class makes it very obvious when ``keywords_with_side_effects()`` has 224 incorrectly interpreted the command line arguments to ``setup.py install`` 225 as one of the 'side effect free' commands or options. 226 """ 227 228 def run(self): 229 raise RuntimeError(setup_requires_error) 230 231 232class DummyPyTest(test): 233 """ 234 This class makes it very obvious when ``keywords_with_side_effects()`` has 235 incorrectly interpreted the command line arguments to ``setup.py test`` as 236 one of the 'side effect free' commands or options. 237 """ 238 239 def run_tests(self): 240 raise RuntimeError(setup_requires_error) 241 242 243with open(os.path.join(base_dir, "README.rst")) as f: 244 long_description = f.read() 245 246 247setup( 248 name=about["__title__"], 249 version=about["__version__"], 250 251 description=about["__summary__"], 252 long_description=long_description, 253 license=about["__license__"], 254 url=about["__uri__"], 255 256 author=about["__author__"], 257 author_email=about["__email__"], 258 259 classifiers=[ 260 "Development Status :: 5 - Production/Stable", 261 "Intended Audience :: Developers", 262 "License :: OSI Approved :: Apache Software License", 263 "License :: OSI Approved :: BSD License", 264 "Natural Language :: English", 265 "Operating System :: MacOS :: MacOS X", 266 "Operating System :: POSIX", 267 "Operating System :: POSIX :: BSD", 268 "Operating System :: POSIX :: Linux", 269 "Operating System :: Microsoft :: Windows", 270 "Programming Language :: Python", 271 "Programming Language :: Python :: 2", 272 "Programming Language :: Python :: 2.7", 273 "Programming Language :: Python :: 3", 274 "Programming Language :: Python :: 3.4", 275 "Programming Language :: Python :: 3.5", 276 "Programming Language :: Python :: 3.6", 277 "Programming Language :: Python :: 3.7", 278 "Programming Language :: Python :: Implementation :: CPython", 279 "Programming Language :: Python :: Implementation :: PyPy", 280 "Topic :: Security :: Cryptography", 281 ], 282 283 package_dir={"": "src"}, 284 packages=find_packages(where="src", exclude=["_cffi_src", "_cffi_src.*"]), 285 include_package_data=True, 286 287 python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', 288 289 install_requires=[ 290 "asn1crypto >= 0.21.0", 291 "six >= 1.4.1", 292 ] + setup_requirements, 293 tests_require=test_requirements, 294 extras_require={ 295 ":python_version < '3'": ["enum34", "ipaddress"], 296 297 "test": test_requirements, 298 "docs": [ 299 "sphinx >= 1.6.5,!=1.8.0", 300 "sphinx_rtd_theme", 301 ], 302 "docstest": [ 303 "doc8", 304 "pyenchant >= 1.6.11", 305 "twine >= 1.12.0", 306 "sphinxcontrib-spelling >= 4.0.1", 307 ], 308 "pep8test": [ 309 "flake8", 310 "flake8-import-order", 311 "pep8-naming", 312 ], 313 # This extra is for the U-label support that was deprecated in 314 # cryptography 2.1. If you need this deprecated path install with 315 # pip install cryptography[idna] 316 "idna": [ 317 "idna >= 2.1", 318 ] 319 }, 320 321 # for cffi 322 zip_safe=False, 323 ext_package="cryptography.hazmat.bindings", 324 **keywords_with_side_effects(sys.argv) 325) 326