setup.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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_openssl_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. if command is 'help':
  125. continue
  126. with open('docs/usage/%s.rst.inc' % command, 'w') as doc:
  127. print('generating help for %s' % command)
  128. params = {"command": command,
  129. "underline": '-' * len('borg ' + command)}
  130. doc.write(".. _borg_{command}:\n\n".format(**params))
  131. doc.write("borg {command}\n{underline}\n::\n\n".format(**params))
  132. epilog = parser.epilog
  133. parser.epilog = None
  134. doc.write(re.sub("^", " ", parser.format_help(), flags=re.M))
  135. doc.write("\nDescription\n~~~~~~~~~~~\n")
  136. doc.write(epilog)
  137. # return to regular Cython configuration, if we changed it
  138. if os.environ.get('BORG_CYTHON_DISABLE') == self.__class__.__name__:
  139. del os.environ['BORG_CYTHON_DISABLE']
  140. class build_api(Command):
  141. description = "generate a basic api.rst file based on the modules available"
  142. user_options = [
  143. ('output=', 'O', 'output directory'),
  144. ]
  145. def initialize_options(self):
  146. pass
  147. def finalize_options(self):
  148. pass
  149. def run(self):
  150. print("auto-generating API documentation")
  151. with open("docs/api.rst", "w") as doc:
  152. doc.write("""
  153. Borg Backup API documentation
  154. =============================
  155. """)
  156. for mod in glob('borg/*.py') + glob('borg/*.pyx'):
  157. print("examining module %s" % mod)
  158. mod = mod.replace('.pyx', '').replace('.py', '').replace('/', '.')
  159. if "._" not in mod:
  160. doc.write("""
  161. .. automodule:: %s
  162. :members:
  163. :undoc-members:
  164. """ % mod)
  165. # (function, predicate), see http://docs.python.org/2/distutils/apiref.html#distutils.cmd.Command.sub_commands
  166. # seems like this doesn't work on RTD, see below for build_py hack.
  167. build.sub_commands.append(('build_api', None))
  168. build.sub_commands.append(('build_usage', None))
  169. class build_py_custom(build_py):
  170. """override build_py to also build our stuff
  171. it is unclear why this is necessary, but in some environments
  172. (Readthedocs.org, specifically), the above
  173. ``build.sub_commands.append()`` doesn't seem to have an effect:
  174. our custom build commands seem to be ignored when running
  175. ``setup.py install``.
  176. This class overrides the ``build_py`` target by forcing it to run
  177. our custom steps as well.
  178. See also the `bug report on RTD
  179. <https://github.com/rtfd/readthedocs.org/issues/1740>`_.
  180. """
  181. def run(self):
  182. super().run()
  183. self.announce('calling custom build steps', level=log.INFO)
  184. self.run_command('build_ext')
  185. self.run_command('build_api')
  186. self.run_command('build_usage')
  187. cmdclass = {
  188. 'build_ext': build_ext,
  189. 'build_api': build_api,
  190. 'build_usage': build_usage,
  191. 'build_py': build_py_custom,
  192. 'sdist': Sdist
  193. }
  194. ext_modules = []
  195. if not on_rtd:
  196. ext_modules += [
  197. Extension('borg.compress', [compress_source], libraries=['lz4'], include_dirs=include_dirs, library_dirs=library_dirs),
  198. Extension('borg.crypto', [crypto_source], libraries=['crypto'], include_dirs=include_dirs, library_dirs=library_dirs),
  199. Extension('borg.chunker', [chunker_source]),
  200. Extension('borg.hashindex', [hashindex_source])
  201. ]
  202. if sys.platform.startswith('linux'):
  203. ext_modules.append(Extension('borg.platform_linux', [platform_linux_source], libraries=['acl']))
  204. elif sys.platform.startswith('freebsd'):
  205. ext_modules.append(Extension('borg.platform_freebsd', [platform_freebsd_source]))
  206. elif sys.platform == 'darwin':
  207. ext_modules.append(Extension('borg.platform_darwin', [platform_darwin_source]))
  208. setup(
  209. name='borgbackup',
  210. use_scm_version={
  211. 'write_to': 'borg/_version.py',
  212. },
  213. author='The Borg Collective (see AUTHORS file)',
  214. author_email='borgbackup@librelist.com',
  215. url='https://borgbackup.github.io/',
  216. description='Deduplicated, encrypted, authenticated and compressed backups',
  217. long_description=long_description,
  218. license='BSD',
  219. platforms=['Linux', 'MacOS X', 'FreeBSD', 'OpenBSD', 'NetBSD', ],
  220. classifiers=[
  221. 'Development Status :: 4 - Beta',
  222. 'Environment :: Console',
  223. 'Intended Audience :: System Administrators',
  224. 'License :: OSI Approved :: BSD License',
  225. 'Operating System :: POSIX :: BSD :: FreeBSD',
  226. 'Operating System :: POSIX :: BSD :: OpenBSD',
  227. 'Operating System :: POSIX :: BSD :: NetBSD',
  228. 'Operating System :: MacOS :: MacOS X',
  229. 'Operating System :: POSIX :: Linux',
  230. 'Programming Language :: Python',
  231. 'Programming Language :: Python :: 3',
  232. 'Programming Language :: Python :: 3.2',
  233. 'Programming Language :: Python :: 3.3',
  234. 'Programming Language :: Python :: 3.4',
  235. 'Programming Language :: Python :: 3.5',
  236. 'Topic :: Security :: Cryptography',
  237. 'Topic :: System :: Archiving :: Backup',
  238. ],
  239. packages=['borg', 'borg.testsuite', 'borg.support', ],
  240. entry_points={
  241. 'console_scripts': [
  242. 'borg = borg.archiver:main',
  243. ]
  244. },
  245. cmdclass=cmdclass,
  246. ext_modules=ext_modules,
  247. setup_requires=['setuptools_scm>=1.7'],
  248. install_requires=install_requires,
  249. )