prepare for new release
[python_pycryptopp.git] / setup.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # Copyright © 2009-2012 Zooko Wilcox-O'Hearn
5 # Author: Zooko Wilcox-O'Hearn
6 #
7 # See README.rst for licensing information.
8
9 import os, platform, re, subprocess, sys
10
11 from setuptools import Extension, setup
12 from setuptools import Command
13 from distutils.util import get_platform
14 from setuptools.command.test import ScanningLoader
15 import unittest
16
17 PKG='pycryptopp'
18 VERSION_PY_FNAME = os.path.join('src', PKG, '_version.py')
19
20 import versioneer
21
22 # ECDSA=False
23 ECDSA=True
24
25 DEBUG=False
26 if "--debug" in sys.argv:
27     DEBUG=True
28     sys.argv.remove("--debug")
29
30 DISABLE_EMBEDDED_CRYPTOPP=False
31 if "--disable-embedded-cryptopp" in sys.argv:
32     DISABLE_EMBEDDED_CRYPTOPP=True
33     sys.argv.remove("--disable-embedded-cryptopp")
34
35 # Unfortunately stdeb v0.3 doesn't seem to offer a way to pass command-line
36 # arguments to setup.py when building for Debian, but it does offer a way to
37 # pass environment variables, so we here check for that in addition to the
38 # command-line argument check above.
39 if os.environ.get('PYCRYPTOPP_DISABLE_EMBEDDED_CRYPTOPP') == "1":
40     DISABLE_EMBEDDED_CRYPTOPP=True
41
42 EMBEDDED_CRYPTOPP_DIR='src-cryptopp'
43
44 BUILD_DOUBLE_LOAD_TESTER=False
45 BDLTARG="--build-double-load-tester"
46 if BDLTARG in sys.argv:
47     BUILD_DOUBLE_LOAD_TESTER=True
48     sys.argv.remove(BDLTARG)
49
50 # There are two ways that this setup.py script can build pycryptopp, either by using the
51 # Crypto++ source code bundled in the pycryptopp source tree, or by linking to a copy of the
52 # Crypto++ library that is already installed on the system.
53
54 extra_compile_args=[]
55 extra_link_args=[]
56 define_macros=[]
57 undef_macros=[]
58 libraries=[]
59 ext_modules=[]
60 include_dirs=[]
61 library_dirs=[]
62 extra_srcs=[] # This is for Crypto++ .cpp files if they are needed.
63
64 #
65 # Fix the build on OpenBSD
66 # http://tahoe-lafs/trac/pycryptopp/ticket/32
67 #
68 if 'openbsd' in platform.system().lower():
69     extra_link_args.append("-fpic")
70
71 if DEBUG:
72     extra_compile_args.append("-O0")
73     extra_compile_args.append("-g")
74     extra_compile_args.append("-Wall")
75     extra_link_args.append("-g")
76     undef_macros.append('NDEBUG')
77 else:
78     extra_compile_args.append("-w")
79
80 if DISABLE_EMBEDDED_CRYPTOPP:
81     define_macros.append(('DISABLE_EMBEDDED_CRYPTOPP', 1))
82
83     # Link with a Crypto++ library that is already installed on the system.
84
85     for inclpath in ["/usr/local/include/cryptopp", "/usr/include/cryptopp"]:
86         if os.path.exists(inclpath):
87             libraries.append("cryptopp")
88             incldir = os.path.dirname(inclpath)
89             include_dirs.append(incldir)
90             libdir = os.path.join(os.path.dirname(incldir), "lib")
91             library_dirs.append(libdir)
92             break
93
94     if not libraries:
95         print "Did not locate libcryptopp in the usual places."
96         print "Adding /usr/local/{include,lib} and -lcryptopp in the hopes"
97         print "that they will work."
98
99         # Note that when using cygwin build tools (including gcc) to build
100         # Windows-native binaries, the os.path.exists() will not see the
101         # /usr/local/include/cryptopp directory but the subsequent call to g++
102         # will.
103         libraries.append("cryptopp")
104         include_dirs.append("/usr/local/include")
105         library_dirs.append("/usr/local/lib")
106
107 else:
108     # Build the bundled Crypto++ library which is included by source
109     # code in the pycryptopp tree and link against it.
110     include_dirs.append(".")
111
112     if 'sunos' in platform.system().lower():
113         extra_compile_args.append('-Wa,--divide') # allow use of "/" operator
114
115     if 'win32' in sys.platform.lower():
116         try:
117             res = subprocess.Popen(['cl'], stdin=open(os.devnull), stdout=subprocess.PIPE).communicate()
118         except EnvironmentError, le:
119             # Okay I guess we're not using the "cl.exe" compiler.
120             using_msvc = False
121         else:
122             using_msvc = True
123     else:
124         using_msvc = False
125
126     if using_msvc:
127         # We can handle out-of-line assembly.
128         cryptopp_src = [ os.path.join(EMBEDDED_CRYPTOPP_DIR, x) for x in os.listdir(EMBEDDED_CRYPTOPP_DIR) if x.endswith(('.cpp', '.asm')) ]
129     else:
130         # We can't handle out-of-line assembly.
131         cryptopp_src = [ os.path.join(EMBEDDED_CRYPTOPP_DIR, x) for x in os.listdir(EMBEDDED_CRYPTOPP_DIR) if x.endswith('.cpp') ]
132
133     # Mac OS X extended attribute files when written to a non-Mac-OS-X
134     # filesystem come out as "._$FNAME", for example "._rdtables.cpp",
135     # and those files contain uncompilable data that is not C++, thus
136     # on occasion causing the build to fail. This works-around that:
137     cryptopp_src = [ c for c in cryptopp_src if not os.path.basename(c).startswith('._') ]
138
139     extra_srcs.extend(cryptopp_src)
140
141 # In either case, we must provide a value for CRYPTOPP_DISABLE_ASM that
142 # matches the one used when Crypto++ was originally compiled. The Crypto++
143 # GNUmakefile tests the assembler version and only enables assembly for
144 # recent versions of the GNU assembler (2.10 or later). The /usr/bin/as on
145 # Mac OS-X 10.6 is too old.
146
147 try:
148     sp = subprocess.Popen(['as', '-v'], stdin=subprocess.PIPE,
149                           stdout=subprocess.PIPE, stderr=subprocess.PIPE,
150                           universal_newlines=True)
151     sp.stdin.close()
152     sp.wait()
153     if re.search("GNU assembler version (0|1|2.0)", sp.stderr.read()):
154         define_macros.append(('CRYPTOPP_DISABLE_ASM', 1))
155 except EnvironmentError:
156     # Okay, nevermind. Maybe there isn't even an 'as' executable on this
157     # platform.
158     pass
159 else:
160     try:
161         # that "as -v" step creates an empty a.out, so clean it up. Modern GNU
162         # "as" has --version, which emits the version number without actually
163         # assembling anything, but older versions only have -v, which emits a
164         # version number and *then* assembles from stdin.
165         os.unlink("a.out")
166     except EnvironmentError:
167         pass
168
169 trove_classifiers=[
170     "Environment :: Console",
171     "License :: OSI Approved :: GNU General Public License (GPL)", # See README.rst for alternative licensing.
172     "License :: DFSG approved",
173     "License :: Other/Proprietary License",
174     "Intended Audience :: Developers",
175     "Operating System :: Microsoft :: Windows",
176     "Operating System :: Unix",
177     "Operating System :: MacOS :: MacOS X",
178     "Natural Language :: English",
179     "Programming Language :: C",
180     "Programming Language :: C++",
181     "Programming Language :: Python",
182     "Programming Language :: Python :: 2",
183     "Programming Language :: Python :: 2.4",
184     "Programming Language :: Python :: 2.5",
185     "Programming Language :: Python :: 2.6",
186     "Programming Language :: Python :: 2.7",
187     "Topic :: Software Development :: Libraries",
188     ]
189
190 srcs = ['src/pycryptopp/_pycryptoppmodule.cpp',
191         'src/pycryptopp/publickey/rsamodule.cpp',
192         'src/pycryptopp/hash/sha256module.cpp',
193         'src/pycryptopp/cipher/aesmodule.cpp',
194         'src/pycryptopp/cipher/xsalsa20module.cpp',
195         ]
196 if ECDSA:
197     srcs.append('src/pycryptopp/publickey/ecdsamodule.cpp')
198 if BUILD_DOUBLE_LOAD_TESTER:
199     srcs.append('_doubleloadtester.cpp', )
200
201 ext_modules.append(
202     Extension('pycryptopp._pycryptopp', extra_srcs + srcs, include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, extra_link_args=extra_link_args, extra_compile_args=extra_compile_args, define_macros=define_macros, undef_macros=undef_macros)
203     )
204
205 # python-ed25519
206 sources = [os.path.join("src-ed25519","glue","ed25519module.c")]
207 sources.extend([os.path.join("src-ed25519","supercop-ref",s)
208                 for s in os.listdir(os.path.join("src-ed25519","supercop-ref"))
209                 if s.endswith(".c") and s!="test.c"])
210 m = Extension("pycryptopp.publickey.ed25519._ed25519",
211               include_dirs=[os.path.join("src-ed25519","supercop-ref")],
212               sources=sources)
213 ext_modules.append(m)
214
215
216 if BUILD_DOUBLE_LOAD_TESTER:
217     ext_modules.append(
218         Extension('_doubleloadtester', extra_srcs + srcs, include_dirs=include_dirs, library_dirs=library_dirs, libraries=libraries, extra_link_args=extra_link_args, extra_compile_args=extra_compile_args, define_macros=define_macros, undef_macros=undef_macros)
219         )
220
221 miscdeps=os.path.join(os.getcwd(), 'misc', 'dependencies')
222 dependency_links=[os.path.join(miscdeps, t) for t in os.listdir(miscdeps) if t.endswith(".tar")]
223 setup_requires = []
224 install_requires = ['setuptools >= 0.6a9'] # for pkg_resources for loading test vectors for unit tests
225
226 # setuptools_pyflakes is needed only if you want "./setup.py flakes" to run
227 # pyflakes on all the pycryptopp modules.
228 if 'flakes' in sys.argv[1:]:
229     setup_requires.append('setuptools_pyflakes >= 1.0.0')
230
231 # stdeb is required to produce Debian files with "sdist_dsc".
232 # http://github.com/astraw/stdeb/tree/master
233 if "sdist_dsc" in sys.argv:
234     setup_requires.append('stdeb')
235
236 data_fnames=['COPYING.GPL', 'COPYING.TGPPL.html', 'README.rst']
237
238 readmetext = open('README.rst').read()
239
240 # In case we are building for a .deb with stdeb's sdist_dsc command, we put the
241 # docs in "share/doc/pycryptopp".
242 doc_loc = "share/doc/" + PKG
243 data_files = [(doc_loc, data_fnames)]
244
245 commands = {}
246
247 ###### Version updating code
248
249 CPP_GIT_VERSION_BODY = '''
250 /* This _version.py is generated from git metadata by the pycryptopp
251  * setup.py. The main version number is taken from the most recent release
252  * tag. If some patches have been added since the last release, this will
253  * have a -NN "build number" suffix, or else a -rNN "revision number" suffix.
254  */
255
256 #define CRYPTOPP_EXTRA_VERSION "%(pkgname)s-%(pkgversion)s"
257 '''
258
259 def get_normalized_version(versions):
260     pieces = versions['version'].split("-")
261
262 # examples: versions:  {'version': '2.3.4-dirty', 'full': '5ebdca46cf83a185710ecb9b29d46ec8ac70de61-dirty'}
263 # examples versions:  {'version': '0.5.29-108-g5ebdca4-dirty', 'full': '5ebdca46cf83a185710ecb9b29d46ec8ac70de61-dirty'}
264 # examples: pieces: ['0.5.29', '108', 'g5ebdca4', 'dirty']
265 # examples: pieces: ['2.3.4', 'dirty']
266 # examples: pieces: ['2.3.4']
267     
268     normalized_version = []
269     normalized_version.append(pieces.pop(0))
270
271     postrelease = None
272     dirty = False
273
274     while len(pieces) > 0:
275         nextpiece = pieces.pop(0)
276         if re.match('\d+$', nextpiece):
277             postrelease = nextpiece
278             normalized_version.append('.'+postrelease)
279         elif nextpiece.startswith('g'):
280             continue
281             # Use the full version instead ,below
282         elif nextpiece == 'dirty':
283             dirty = True
284
285     fullvhex = versions['full'].split('-')[0]
286     full = int(fullvhex, 16)
287     normalized_version.append('.'+str(full))
288
289     if postrelease is not None:
290         normalized_version.append('.post'+postrelease)
291     if dirty is True:
292         normalized_version.append('.dev0')
293
294     return ''.join(normalized_version)
295
296 def read_version_py(infname):
297     try:
298         verstrline = open(infname, "rt").read()
299     except EnvironmentError:
300         return None
301     else:
302         VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
303         mo = re.search(VSRE, verstrline, re.M)
304         if mo:
305             return mo.group(1)
306
307 EXTRAVERSION_H_FNAME = os.path.join(EMBEDDED_CRYPTOPP_DIR, 'extraversion.h')
308
309 VERSION_BODY = '''
310 # This is the version of this tree, as created by %(versiontool)s from the
311 # git information: the main version number is taken from the most recent
312 # release tag. If some patches have been added since the last release, this
313 # will have a -NN "build number" suffix, followed by -gXXX "revid" suffix.
314
315 __pkgname__ = "%(pkgname)s"
316 __version__ = "%(pkgversion)s"
317 '''
318
319 class UpdateVersion(object):
320     def run(self):
321
322         versions = versioneer.versions_from_vcs(PKG+'-', '.')
323         assert isinstance(versions, dict)
324
325         vers_f_file = read_version_py(VERSION_PY_FNAME)
326
327         if not versions and vers_f_file is None:
328             raise Exception("problem: couldn't get version information from revision control history, and there is no version information in '%s'. Stopping." % (VERSION_PY_FNAME,))
329
330         if versions:
331             version = get_normalized_version(versions)
332         else:
333             version = vers_f_file
334
335         # Let's avoid touching the change time (ctime) on the files unless
336         # they actually need to be updated.
337
338         if self.read_extraversion_h(EXTRAVERSION_H_FNAME) != version:
339             self.write_extraversion_h(
340                 PKG,
341                 version,
342                 EXTRAVERSION_H_FNAME,
343                 CPP_GIT_VERSION_BODY
344                 )
345
346         if read_version_py(VERSION_PY_FNAME) != version:
347             self.write_version_py(
348                 PKG,
349                 version,
350                 VERSION_PY_FNAME,
351                 VERSION_BODY,
352                 "pycryptopp's setup.py"
353                 )
354
355         return version
356
357     def write_version_py(self, pkgname, version, outfname, body, EXE_NAME):
358         f = open(outfname, "wb+")
359         f.write(body % {
360                 'versiontool': EXE_NAME,
361                 'pkgversion': version,
362                 'pkgname': pkgname,
363                 })
364         f.close()
365
366     def write_extraversion_h(self, pkgname, version, outfname, body):
367         f = open(outfname, "wb")
368         f.write(body % {"pkgname": pkgname, "pkgversion": version})
369         f.close()
370
371     def read_extraversion_h(self, infname):
372         try:
373             verstrline = open(infname, "rt").read()
374         except EnvironmentError:
375             return None
376         else:
377             VSRE = r"^#define CRYPTOPP_EXTRA_VERSION +\"([^\"]*)\""
378             mo = re.search(VSRE, verstrline, re.M)
379             if mo:
380                 return mo.group(1)
381
382 version = UpdateVersion().run()
383
384 class Test(Command):
385     description = "run tests"
386     user_options = []
387     def initialize_options(self):
388         self.test_suite = None
389     def finalize_options(self):
390         if self.test_suite is None:
391             self.test_suite = self.distribution.test_suite
392     def setup_path(self):
393         # copied from distutils/command/build.py
394         self.plat_name = get_platform()
395         plat_specifier = ".%s-%s" % (self.plat_name, sys.version[0:3])
396         self.build_lib = os.path.join("build", "lib"+plat_specifier)
397         sys.path.insert(0, self.build_lib)
398     def run(self):
399         self.setup_path()
400         loader = ScanningLoader()
401         test = loader.loadTestsFromName(self.test_suite)
402         runner = unittest.TextTestRunner(verbosity=2)
403         result = runner.run(test)
404         sys.exit(not result.wasSuccessful())
405 commands["test"] = Test
406
407 setup(name=PKG,
408       version=version,
409       description='Python wrappers for a few algorithms from the Crypto++ library',
410       long_description=readmetext,
411       author='Zooko Wilcox-O\'Hearn',
412       author_email='zooko@zooko.com',
413       url='https://tahoe-lafs.org/trac/' + PKG,
414       license='GNU GPL', # see README.rst for details -- there is also an alternative licence
415       packages=["pycryptopp",
416                 "pycryptopp.cipher",
417                 "pycryptopp.hash",
418                 "pycryptopp.publickey",
419                 "pycryptopp.publickey.ed25519",
420                 "pycryptopp.test",
421                 ],
422       include_package_data=True,
423       exclude_package_data={
424           '': [ '*.cpp', '*.hpp', ]
425           },
426       data_files=data_files,
427       package_dir={"pycryptopp": "src/pycryptopp"},
428       setup_requires=setup_requires,
429       install_requires=install_requires,
430       dependency_links=dependency_links,
431       classifiers=trove_classifiers,
432       ext_modules=ext_modules,
433       test_suite=PKG+".test",
434       zip_safe=False, # I prefer unzipped for easier access.
435       cmdclass=commands,
436       )