You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

_setup.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. # -*- test-case-name: twisted.python.test.test_setup -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. # pylint: disable=I0011,C0103,C9302,W9401,W9402
  5. """
  6. Setuptools convenience functionality.
  7. This file must not import anything from Twisted, as it is loaded by C{exec} in
  8. C{setup.py}. If you need compatibility functions for this code, duplicate them
  9. here.
  10. @var _EXTRA_OPTIONS: These are the actual package names and versions that will
  11. be used by C{extras_require}. This is not passed to setup directly so that
  12. combinations of the packages can be created without the need to copy
  13. package names multiple times.
  14. @var _EXTRAS_REQUIRE: C{extras_require} is a dictionary of items that can be
  15. passed to setup.py to install optional dependencies. For example, to
  16. install the optional dev dependencies one would type::
  17. pip install -e ".[dev]"
  18. This has been supported by setuptools since 0.5a4.
  19. @var _PLATFORM_INDEPENDENT: A list of all optional cross-platform dependencies,
  20. as setuptools version specifiers, used to populate L{_EXTRAS_REQUIRE}.
  21. @var _EXTENSIONS: The list of L{ConditionalExtension} used by the setup
  22. process.
  23. @var notPortedModules: Modules that are not yet ported to Python 3.
  24. """
  25. import io
  26. import os
  27. import platform
  28. import re
  29. import sys
  30. from distutils.command import build_ext
  31. from distutils.errors import CompileError
  32. from setuptools import Extension, find_packages
  33. from setuptools.command.build_py import build_py
  34. # Do not replace this with t.p.compat imports, this file must not import
  35. # from Twisted. See the docstring.
  36. if sys.version_info < (3, 0):
  37. _PY3 = False
  38. else:
  39. _PY3 = True
  40. STATIC_PACKAGE_METADATA = dict(
  41. name="Twisted",
  42. description="An asynchronous networking framework written in Python",
  43. author="Twisted Matrix Laboratories",
  44. author_email="twisted-python@twistedmatrix.com",
  45. maintainer="Glyph Lefkowitz",
  46. maintainer_email="glyph@twistedmatrix.com",
  47. url="https://twistedmatrix.com/",
  48. project_urls={
  49. 'Documentation': 'https://twistedmatrix.com/documents/current/',
  50. 'Source': 'https://github.com/twisted/twisted',
  51. 'Issues': 'https://twistedmatrix.com/trac/report',
  52. },
  53. license="MIT",
  54. classifiers=[
  55. "Programming Language :: Python :: 2.7",
  56. "Programming Language :: Python :: 3",
  57. "Programming Language :: Python :: 3.5",
  58. "Programming Language :: Python :: 3.6",
  59. "Programming Language :: Python :: 3.7",
  60. ],
  61. )
  62. _dev = [
  63. 'pyflakes >= 1.0.0',
  64. 'twisted-dev-tools >= 0.0.2',
  65. 'python-subunit',
  66. 'sphinx >= 1.3.1',
  67. 'towncrier >= 17.4.0'
  68. ]
  69. if not _PY3:
  70. # These modules do not yet work on Python 3.
  71. _dev += [
  72. 'twistedchecker >= 0.4.0',
  73. 'pydoctor >= 16.2.0',
  74. ]
  75. _EXTRA_OPTIONS = dict(
  76. dev=_dev,
  77. tls=[
  78. 'pyopenssl >= 16.0.0',
  79. # service_identity 18.1.0 added support for validating IP addresses in
  80. # certificate subjectAltNames
  81. 'service_identity >= 18.1.0',
  82. # idna 2.3 introduced some changes that break a few things. Avoid it.
  83. # The problems were fixed in 2.4.
  84. 'idna >= 0.6, != 2.3',
  85. ],
  86. conch=[
  87. 'pyasn1',
  88. 'cryptography >= 2.5',
  89. 'appdirs >= 1.4.0',
  90. 'bcrypt >= 3.0.0',
  91. ],
  92. soap=['soappy'],
  93. serial=['pyserial >= 3.0',
  94. 'pywin32; platform_system == "Windows"'],
  95. macos=['pyobjc-core',
  96. 'pyobjc-framework-CFNetwork',
  97. 'pyobjc-framework-Cocoa'],
  98. windows=['pywin32'],
  99. http2=['h2 >= 3.0, < 4.0',
  100. 'priority >= 1.1.0, < 2.0'],
  101. )
  102. _PLATFORM_INDEPENDENT = (
  103. _EXTRA_OPTIONS['tls'] +
  104. _EXTRA_OPTIONS['conch'] +
  105. _EXTRA_OPTIONS['soap'] +
  106. _EXTRA_OPTIONS['serial'] +
  107. _EXTRA_OPTIONS['http2']
  108. )
  109. _EXTRAS_REQUIRE = {
  110. 'dev': _EXTRA_OPTIONS['dev'],
  111. 'tls': _EXTRA_OPTIONS['tls'],
  112. 'conch': _EXTRA_OPTIONS['conch'],
  113. 'soap': _EXTRA_OPTIONS['soap'],
  114. 'serial': _EXTRA_OPTIONS['serial'],
  115. 'http2': _EXTRA_OPTIONS['http2'],
  116. 'all_non_platform': _PLATFORM_INDEPENDENT,
  117. 'macos_platform': (
  118. _EXTRA_OPTIONS['macos'] + _PLATFORM_INDEPENDENT
  119. ),
  120. 'windows_platform': (
  121. _EXTRA_OPTIONS['windows'] + _PLATFORM_INDEPENDENT
  122. ),
  123. }
  124. _EXTRAS_REQUIRE['osx_platform'] = _EXTRAS_REQUIRE['macos_platform']
  125. # Scripts provided by Twisted on Python 2 and 3.
  126. _CONSOLE_SCRIPTS = [
  127. "ckeygen = twisted.conch.scripts.ckeygen:run",
  128. "cftp = twisted.conch.scripts.cftp:run",
  129. "conch = twisted.conch.scripts.conch:run",
  130. "mailmail = twisted.mail.scripts.mailmail:run",
  131. "pyhtmlizer = twisted.scripts.htmlizer:run",
  132. "tkconch = twisted.conch.scripts.tkconch:run",
  133. "trial = twisted.scripts.trial:run",
  134. "twist = twisted.application.twist._twist:Twist.main",
  135. "twistd = twisted.scripts.twistd:run",
  136. ]
  137. class ConditionalExtension(Extension, object):
  138. """
  139. An extension module that will only be compiled if certain conditions are
  140. met.
  141. @param condition: A callable of one argument which returns True or False to
  142. indicate whether the extension should be built. The argument is an
  143. instance of L{build_ext_twisted}, which has useful methods for checking
  144. things about the platform.
  145. """
  146. def __init__(self, *args, **kwargs):
  147. self.condition = kwargs.pop("condition", lambda builder: True)
  148. Extension.__init__(self, *args, **kwargs)
  149. # The C extensions used for Twisted.
  150. _EXTENSIONS = [
  151. ConditionalExtension(
  152. "twisted.test.raiser",
  153. sources=["src/twisted/test/raiser.c"],
  154. condition=lambda _: _isCPython),
  155. ConditionalExtension(
  156. "twisted.internet.iocpreactor.iocpsupport",
  157. sources=[
  158. "src/twisted/internet/iocpreactor/iocpsupport/iocpsupport.c",
  159. "src/twisted/internet/iocpreactor/iocpsupport/winsock_pointers.c",
  160. ],
  161. libraries=["ws2_32"],
  162. condition=lambda _: _isCPython and sys.platform == "win32"),
  163. ConditionalExtension(
  164. "twisted.python._sendmsg",
  165. sources=["src/twisted/python/_sendmsg.c"],
  166. condition=lambda _: not _PY3 and sys.platform != "win32"),
  167. ]
  168. def _checkPythonVersion():
  169. """
  170. Fail if we detect a version of Python we don't support.
  171. """
  172. version = getattr(sys, "version_info", (0,))
  173. if version < (2, 7):
  174. raise ImportError("Twisted requires Python 2.7 or later.")
  175. elif version >= (3, 0) and version < (3, 5):
  176. raise ImportError("Twisted on Python 3 requires Python 3.5 or later.")
  177. def _longDescriptionArgsFromReadme(readme):
  178. """
  179. Generate a PyPI long description from the readme.
  180. @param readme: Path to the readme reStructuredText file.
  181. @type readme: C{str}
  182. @return: Keyword arguments to be passed to C{setuptools.setup()}.
  183. @rtype: C{str}
  184. """
  185. with io.open(readme, encoding='utf-8') as f:
  186. readmeRst = f.read()
  187. # Munge links of the form `NEWS <NEWS.rst>`_ to point at the appropriate
  188. # location on GitHub so that they function when the long description is
  189. # displayed on PyPI.
  190. longDesc = re.sub(
  191. r'`([^`]+)\s+<(?!https?://)([^>]+)>`_',
  192. r'`\1 <https://github.com/twisted/twisted/blob/trunk/\2>`_',
  193. readmeRst,
  194. flags=re.I,
  195. )
  196. return {
  197. 'long_description': longDesc,
  198. 'long_description_content_type': 'text/x-rst',
  199. }
  200. def getSetupArgs(extensions=_EXTENSIONS, readme='README.rst'):
  201. """
  202. Generate arguments for C{setuptools.setup()}
  203. @param extensions: C extension modules to maybe build. This argument is to
  204. be used for testing.
  205. @type extensions: C{list} of C{ConditionalExtension}
  206. @param readme: Path to the readme reStructuredText file. This argument is
  207. to be used for testing.
  208. @type readme: C{str}
  209. @return: The keyword arguments to be used by the setup method.
  210. @rtype: L{dict}
  211. """
  212. _checkPythonVersion()
  213. arguments = STATIC_PACKAGE_METADATA.copy()
  214. if readme:
  215. arguments.update(_longDescriptionArgsFromReadme(readme))
  216. # This is a workaround for distutils behavior; ext_modules isn't
  217. # actually used by our custom builder. distutils deep-down checks
  218. # to see if there are any ext_modules defined before invoking
  219. # the build_ext command. We need to trigger build_ext regardless
  220. # because it is the thing that does the conditional checks to see
  221. # if it should build any extensions. The reason we have to delay
  222. # the conditional checks until then is that the compiler objects
  223. # are not yet set up when this code is executed.
  224. arguments["ext_modules"] = extensions
  225. # Use custome class to build the extensions.
  226. class my_build_ext(build_ext_twisted):
  227. conditionalExtensions = extensions
  228. command_classes = {
  229. 'build_ext': my_build_ext,
  230. }
  231. if sys.version_info[0] >= 3:
  232. command_classes['build_py'] = BuildPy3
  233. requirements = [
  234. "zope.interface >= 4.4.2",
  235. "constantly >= 15.1",
  236. "incremental >= 16.10.1",
  237. "Automat >= 0.3.0",
  238. "hyperlink >= 17.1.1",
  239. "PyHamcrest >= 1.9.0",
  240. "attrs >= 17.4.0",
  241. ]
  242. arguments.update(dict(
  243. packages=find_packages("src"),
  244. use_incremental=True,
  245. setup_requires=["incremental >= 16.10.1"],
  246. install_requires=requirements,
  247. entry_points={
  248. 'console_scripts': _CONSOLE_SCRIPTS
  249. },
  250. cmdclass=command_classes,
  251. include_package_data=True,
  252. exclude_package_data={
  253. "": ["*.c", "*.h", "*.pxi", "*.pyx", "build.bat"],
  254. },
  255. zip_safe=False,
  256. extras_require=_EXTRAS_REQUIRE,
  257. package_dir={"": "src"},
  258. ))
  259. return arguments
  260. class BuildPy3(build_py, object):
  261. """
  262. A version of build_py that doesn't install the modules that aren't yet
  263. ported to Python 3.
  264. """
  265. def find_package_modules(self, package, package_dir):
  266. modules = [
  267. module for module
  268. in build_py.find_package_modules(self, package, package_dir)
  269. if ".".join([module[0], module[1]]) not in notPortedModules]
  270. return modules
  271. ## Helpers and distutil tweaks
  272. class build_ext_twisted(build_ext.build_ext, object):
  273. """
  274. Allow subclasses to easily detect and customize Extensions to
  275. build at install-time.
  276. """
  277. def prepare_extensions(self):
  278. """
  279. Prepare the C{self.extensions} attribute (used by
  280. L{build_ext.build_ext}) by checking which extensions in
  281. I{conditionalExtensions} should be built. In addition, if we are
  282. building on NT, define the WIN32 macro to 1.
  283. """
  284. # always define WIN32 under Windows
  285. if os.name == 'nt':
  286. self.define_macros = [("WIN32", 1)]
  287. else:
  288. self.define_macros = []
  289. # On Solaris 10, we need to define the _XOPEN_SOURCE and
  290. # _XOPEN_SOURCE_EXTENDED macros to build in order to gain access to
  291. # the msg_control, msg_controllen, and msg_flags members in
  292. # sendmsg.c. (according to
  293. # https://stackoverflow.com/questions/1034587). See the documentation
  294. # of X/Open CAE in the standards(5) man page of Solaris.
  295. if sys.platform.startswith('sunos'):
  296. self.define_macros.append(('_XOPEN_SOURCE', 1))
  297. self.define_macros.append(('_XOPEN_SOURCE_EXTENDED', 1))
  298. self.extensions = [
  299. x for x in self.conditionalExtensions if x.condition(self)
  300. ]
  301. for ext in self.extensions:
  302. ext.define_macros.extend(self.define_macros)
  303. def build_extensions(self):
  304. """
  305. Check to see which extension modules to build and then build them.
  306. """
  307. self.prepare_extensions()
  308. build_ext.build_ext.build_extensions(self)
  309. def _remove_conftest(self):
  310. for filename in ("conftest.c", "conftest.o", "conftest.obj"):
  311. try:
  312. os.unlink(filename)
  313. except EnvironmentError:
  314. pass
  315. def _compile_helper(self, content):
  316. conftest = open("conftest.c", "w")
  317. try:
  318. with conftest:
  319. conftest.write(content)
  320. try:
  321. self.compiler.compile(["conftest.c"], output_dir='')
  322. except CompileError:
  323. return False
  324. return True
  325. finally:
  326. self._remove_conftest()
  327. def _check_header(self, header_name):
  328. """
  329. Check if the given header can be included by trying to compile a file
  330. that contains only an #include line.
  331. """
  332. self.compiler.announce("checking for {} ...".format(header_name), 0)
  333. return self._compile_helper("#include <{}>\n".format(header_name))
  334. def _checkCPython(sys=sys, platform=platform):
  335. """
  336. Checks if this implementation is CPython.
  337. This uses C{platform.python_implementation}.
  338. This takes C{sys} and C{platform} kwargs that by default use the real
  339. modules. You shouldn't care about these -- they are for testing purposes
  340. only.
  341. @return: C{False} if the implementation is definitely not CPython, C{True}
  342. otherwise.
  343. """
  344. return platform.python_implementation() == "CPython"
  345. _isCPython = _checkCPython()
  346. notPortedModules = [
  347. "twisted.mail.alias",
  348. "twisted.mail.bounce",
  349. "twisted.mail.mail",
  350. "twisted.mail.maildir",
  351. "twisted.mail.pb",
  352. "twisted.mail.relaymanager",
  353. "twisted.mail.scripts.__init__",
  354. "twisted.mail.tap",
  355. "twisted.mail.test.test_bounce",
  356. "twisted.mail.test.test_mail",
  357. "twisted.mail.test.test_options",
  358. "twisted.mail.test.test_scripts",
  359. "twisted.news.__init__",
  360. "twisted.news.database",
  361. "twisted.news.news",
  362. "twisted.news.nntp",
  363. "twisted.news.tap",
  364. "twisted.news.test.__init__",
  365. "twisted.news.test.test_database",
  366. "twisted.news.test.test_news",
  367. "twisted.news.test.test_nntp",
  368. "twisted.plugins.twisted_mail",
  369. "twisted.plugins.twisted_news",
  370. "twisted.protocols.shoutcast",
  371. "twisted.python._pydoctor",
  372. "twisted.python.finalize",
  373. "twisted.python.hook",
  374. "twisted.python.test.cmodulepullpipe",
  375. "twisted.python.test.test_pydoctor",
  376. "twisted.python.test.test_win32",
  377. "twisted.test.test_hook",
  378. "twisted.web.soap",
  379. "twisted.web.test.test_soap",
  380. ]