2
0

setup.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. # -*- encoding: utf-8 *-*
  2. import os
  3. import re
  4. import sys
  5. from glob import glob
  6. from distutils.command.build import build
  7. from distutils.core import Command
  8. from distutils.errors import DistutilsOptionError
  9. from distutils import log
  10. from setuptools.command.build_py import build_py
  11. min_python = (3, 2)
  12. my_python = sys.version_info
  13. if my_python < min_python:
  14. print("Borg requires Python %d.%d or later" % min_python)
  15. sys.exit(1)
  16. # Are we building on ReadTheDocs?
  17. on_rtd = os.environ.get('READTHEDOCS')
  18. # msgpack pure python data corruption was fixed in 0.4.6.
  19. # Also, we might use some rather recent API features.
  20. install_requires=['msgpack-python>=0.4.6', ]
  21. from setuptools import setup, Extension
  22. from setuptools.command.sdist import sdist
  23. compress_source = 'borg/compress.pyx'
  24. crypto_source = 'borg/crypto.pyx'
  25. chunker_source = 'borg/chunker.pyx'
  26. hashindex_source = 'borg/hashindex.pyx'
  27. platform_linux_source = 'borg/platform_linux.pyx'
  28. platform_darwin_source = 'borg/platform_darwin.pyx'
  29. platform_freebsd_source = 'borg/platform_freebsd.pyx'
  30. try:
  31. from Cython.Distutils import build_ext
  32. import Cython.Compiler.Main as cython_compiler
  33. class Sdist(sdist):
  34. def __init__(self, *args, **kwargs):
  35. for src in glob('borg/*.pyx'):
  36. cython_compiler.compile(src, cython_compiler.default_options)
  37. super().__init__(*args, **kwargs)
  38. def make_distribution(self):
  39. self.filelist.extend([
  40. 'borg/compress.c',
  41. 'borg/crypto.c',
  42. 'borg/chunker.c', 'borg/_chunker.c',
  43. 'borg/hashindex.c', 'borg/_hashindex.c',
  44. 'borg/platform_linux.c',
  45. 'borg/platform_freebsd.c',
  46. 'borg/platform_darwin.c',
  47. ])
  48. super().make_distribution()
  49. except ImportError:
  50. class Sdist(sdist):
  51. def __init__(self, *args, **kwargs):
  52. raise Exception('Cython is required to run sdist')
  53. compress_source = compress_source.replace('.pyx', '.c')
  54. crypto_source = crypto_source.replace('.pyx', '.c')
  55. chunker_source = chunker_source.replace('.pyx', '.c')
  56. hashindex_source = hashindex_source.replace('.pyx', '.c')
  57. platform_linux_source = platform_linux_source.replace('.pyx', '.c')
  58. platform_freebsd_source = platform_freebsd_source.replace('.pyx', '.c')
  59. platform_darwin_source = platform_darwin_source.replace('.pyx', '.c')
  60. from distutils.command.build_ext import build_ext
  61. if not on_rtd and not all(os.path.exists(path) for path in [
  62. compress_source, crypto_source, chunker_source, hashindex_source,
  63. platform_linux_source, platform_freebsd_source]):
  64. raise ImportError('The GIT version of Borg needs Cython. Install Cython or use a released version.')
  65. def detect_openssl(prefixes):
  66. for prefix in prefixes:
  67. filename = os.path.join(prefix, 'include', 'openssl', 'evp.h')
  68. if os.path.exists(filename):
  69. with open(filename, 'r') as fd:
  70. if 'PKCS5_PBKDF2_HMAC(' in fd.read():
  71. return prefix
  72. def detect_lz4(prefixes):
  73. for prefix in prefixes:
  74. filename = os.path.join(prefix, 'include', 'lz4.h')
  75. if os.path.exists(filename):
  76. with open(filename, 'r') as fd:
  77. if 'LZ4_decompress_safe' in fd.read():
  78. return prefix
  79. include_dirs = []
  80. library_dirs = []
  81. possible_openssl_prefixes = ['/usr', '/usr/local', '/usr/local/opt/openssl', '/usr/local/ssl', '/usr/local/openssl', '/usr/local/borg', '/opt/local']
  82. if os.environ.get('BORG_OPENSSL_PREFIX'):
  83. possible_openssl_prefixes.insert(0, os.environ.get('BORG_OPENSSL_PREFIX'))
  84. ssl_prefix = detect_openssl(possible_openssl_prefixes)
  85. if not ssl_prefix:
  86. raise Exception('Unable to find OpenSSL >= 1.0 headers. (Looked here: {})'.format(', '.join(possible_openssl_prefixes)))
  87. include_dirs.append(os.path.join(ssl_prefix, 'include'))
  88. library_dirs.append(os.path.join(ssl_prefix, 'lib'))
  89. possible_lz4_prefixes = ['/usr', '/usr/local', '/usr/local/opt/lz4', '/usr/local/lz4', '/usr/local/borg', '/opt/local']
  90. if os.environ.get('BORG_LZ4_PREFIX'):
  91. possible_lz4_prefixes.insert(0, os.environ.get('BORG_LZ4_PREFIX'))
  92. lz4_prefix = detect_lz4(possible_lz4_prefixes)
  93. if lz4_prefix:
  94. include_dirs.append(os.path.join(lz4_prefix, 'include'))
  95. library_dirs.append(os.path.join(lz4_prefix, 'lib'))
  96. elif not on_rtd:
  97. raise Exception('Unable to find LZ4 headers. (Looked here: {})'.format(', '.join(possible_lz4_prefixes)))
  98. with open('README.rst', 'r') as fd:
  99. long_description = fd.read()
  100. class build_usage(Command):
  101. description = "generate usage for each command"
  102. user_options = [
  103. ('output=', 'O', 'output directory'),
  104. ]
  105. def initialize_options(self):
  106. pass
  107. def finalize_options(self):
  108. pass
  109. def run(self):
  110. print('generating usage docs')
  111. # allows us to build docs without the C modules fully loaded during help generation
  112. if 'BORG_CYTHON_DISABLE' not in os.environ:
  113. os.environ['BORG_CYTHON_DISABLE'] = self.__class__.__name__
  114. from borg.archiver import Archiver
  115. parser = Archiver().build_parser(prog='borg')
  116. choices = {}
  117. for action in parser._actions:
  118. if action.choices is not None:
  119. choices.update(action.choices)
  120. print('found commands: %s' % list(choices.keys()))
  121. if not os.path.exists('docs/usage'):
  122. os.mkdir('docs/usage')
  123. for command, parser in choices.items():
  124. print('generating help for %s' % command)
  125. with open('docs/usage/%s.rst.inc' % command, 'w') as doc:
  126. if command == 'help':
  127. for topic in Archiver.helptext:
  128. params = {"topic": topic,
  129. "underline": '~' * len('borg help ' + topic)}
  130. doc.write(".. _borg_{topic}:\n\n".format(**params))
  131. doc.write("borg help {topic}\n{underline}\n::\n\n".format(**params))
  132. doc.write(Archiver.helptext[topic])
  133. else:
  134. params = {"command": command,
  135. "underline": '-' * len('borg ' + command)}
  136. doc.write(".. _borg_{command}:\n\n".format(**params))
  137. doc.write("borg {command}\n{underline}\n::\n\n".format(**params))
  138. epilog = parser.epilog
  139. parser.epilog = None
  140. doc.write(re.sub("^", " ", parser.format_help(), flags=re.M))
  141. doc.write("\nDescription\n~~~~~~~~~~~\n")
  142. doc.write(epilog)
  143. # return to regular Cython configuration, if we changed it
  144. if os.environ.get('BORG_CYTHON_DISABLE') == self.__class__.__name__:
  145. del os.environ['BORG_CYTHON_DISABLE']
  146. class build_api(Command):
  147. description = "generate a basic api.rst file based on the modules available"
  148. user_options = [
  149. ('output=', 'O', 'output directory'),
  150. ]
  151. def initialize_options(self):
  152. pass
  153. def finalize_options(self):
  154. pass
  155. def run(self):
  156. print("auto-generating API documentation")
  157. with open("docs/api.rst", "w") as doc:
  158. doc.write("""
  159. API Documentation
  160. =================
  161. """)
  162. for mod in glob('borg/*.py') + glob('borg/*.pyx'):
  163. print("examining module %s" % mod)
  164. mod = mod.replace('.pyx', '').replace('.py', '').replace('/', '.')
  165. if "._" not in mod:
  166. doc.write("""
  167. .. automodule:: %s
  168. :members:
  169. :undoc-members:
  170. """ % mod)
  171. # (function, predicate), see http://docs.python.org/2/distutils/apiref.html#distutils.cmd.Command.sub_commands
  172. # seems like this doesn't work on RTD, see below for build_py hack.
  173. build.sub_commands.append(('build_api', None))
  174. build.sub_commands.append(('build_usage', None))
  175. class build_py_custom(build_py):
  176. """override build_py to also build our stuff
  177. it is unclear why this is necessary, but in some environments
  178. (Readthedocs.org, specifically), the above
  179. ``build.sub_commands.append()`` doesn't seem to have an effect:
  180. our custom build commands seem to be ignored when running
  181. ``setup.py install``.
  182. This class overrides the ``build_py`` target by forcing it to run
  183. our custom steps as well.
  184. See also the `bug report on RTD
  185. <https://github.com/rtfd/readthedocs.org/issues/1740>`_.
  186. """
  187. def run(self):
  188. super().run()
  189. self.announce('calling custom build steps', level=log.INFO)
  190. self.run_command('build_ext')
  191. self.run_command('build_api')
  192. self.run_command('build_usage')
  193. cmdclass = {
  194. 'build_ext': build_ext,
  195. 'build_api': build_api,
  196. 'build_usage': build_usage,
  197. 'build_py': build_py_custom,
  198. 'sdist': Sdist
  199. }
  200. ext_modules = []
  201. if not on_rtd:
  202. ext_modules += [
  203. Extension('borg.compress', [compress_source], libraries=['lz4'], include_dirs=include_dirs, library_dirs=library_dirs),
  204. Extension('borg.crypto', [crypto_source], libraries=['crypto'], include_dirs=include_dirs, library_dirs=library_dirs),
  205. Extension('borg.chunker', [chunker_source]),
  206. Extension('borg.hashindex', [hashindex_source])
  207. ]
  208. if sys.platform.startswith('linux'):
  209. ext_modules.append(Extension('borg.platform_linux', [platform_linux_source], libraries=['acl']))
  210. elif sys.platform.startswith('freebsd'):
  211. ext_modules.append(Extension('borg.platform_freebsd', [platform_freebsd_source]))
  212. elif sys.platform == 'darwin':
  213. ext_modules.append(Extension('borg.platform_darwin', [platform_darwin_source]))
  214. setup(
  215. name='borgbackup',
  216. use_scm_version={
  217. 'write_to': 'borg/_version.py',
  218. },
  219. author='The Borg Collective (see AUTHORS file)',
  220. author_email='borgbackup@librelist.com',
  221. url='https://borgbackup.readthedocs.org/',
  222. description='Deduplicated, encrypted, authenticated and compressed backups',
  223. long_description=long_description,
  224. license='BSD',
  225. platforms=['Linux', 'MacOS X', 'FreeBSD', 'OpenBSD', 'NetBSD', ],
  226. classifiers=[
  227. 'Development Status :: 4 - Beta',
  228. 'Environment :: Console',
  229. 'Intended Audience :: System Administrators',
  230. 'License :: OSI Approved :: BSD License',
  231. 'Operating System :: POSIX :: BSD :: FreeBSD',
  232. 'Operating System :: POSIX :: BSD :: OpenBSD',
  233. 'Operating System :: POSIX :: BSD :: NetBSD',
  234. 'Operating System :: MacOS :: MacOS X',
  235. 'Operating System :: POSIX :: Linux',
  236. 'Programming Language :: Python',
  237. 'Programming Language :: Python :: 3',
  238. 'Programming Language :: Python :: 3.2',
  239. 'Programming Language :: Python :: 3.3',
  240. 'Programming Language :: Python :: 3.4',
  241. 'Programming Language :: Python :: 3.5',
  242. 'Topic :: Security :: Cryptography',
  243. 'Topic :: System :: Archiving :: Backup',
  244. ],
  245. packages=['borg', 'borg.testsuite', 'borg.support', ],
  246. entry_points={
  247. 'console_scripts': [
  248. 'borg = borg.archiver:main',
  249. ]
  250. },
  251. cmdclass=cmdclass,
  252. ext_modules=ext_modules,
  253. setup_requires=['setuptools_scm>=1.7'],
  254. install_requires=install_requires,
  255. )