setup.py 11 KB

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