1#!/usr/bin/env python
2
3#
4# Copyright (c) 2008-2010 Stefan Krah. All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12#
13# 2. Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in the
15#    documentation and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27# SUCH DAMAGE.
28#
29
30
31import sys, os, platform, re
32from shutil import copy2
33from glob import glob
34from distutils import sysconfig
35from distutils.core import setup, Extension
36
37
38DESCRIPTION = """\
39Fast arbitrary precision correctly-rounded decimal floating point arithmetic.\
40"""
41
42LONG_DESCRIPTION = """
43Overview
44--------
45
46The ``cdecimal`` package is a fast drop-in replacement for the ``decimal`` module
47in Python's standard library. Both modules provide complete implementations of
48Mike Cowlishaw/IBM's ``General Decimal Arithmetic Specification``.
49
50Testing
51-------
52
53Both ``cdecimal`` and the underlying library - ``libmpdec`` - are extremely
54well tested. ``libmpdec`` is one of the few open source projects with 100%
55code coverage. ``cdecimal`` is rigorously tested against ``decimal.py``.
56
57Short benchmarks
58----------------
59
60Typical performance gains are between 30x for I/O heavy benchmarks
61and 80x for numerical programs. In a database benchmark, cdecimal
62exhibits a speedup of 12x over decimal.py.
63
64+---------+-------------+--------------+-------------+
65|         |   decimal   |   cdecimal   |   speedup   |
66+=========+=============+==============+=============+
67|   pi    |    42.75s   |    0.58s     |     74x     |
68+---------+-------------+--------------+-------------+
69| telco   |   172.19s   |    5.68s     |     30x     |
70+---------+-------------+--------------+-------------+
71| psycopg |     3.57s   |    0.29s     |     12x     |
72+---------+-------------+--------------+-------------+
73
74Documentation
75-------------
76
77Since ``cdecimal`` is compatible with ``decimal.py``, the official documentation
78is valid. For the few remaining differences, refer to the second link.
79
80* `Decimal module <http://docs.python.org/dev/py3k/library/decimal.html>`_
81* `Differences between cdecimal and decimal <http://www.bytereef.org/mpdecimal/doc/cdecimal/index.html>`_
82
83Linux Notes
84-----------
85
86The build process requires a working C compiler and a *full* Python install with
87development headers. Linux distributions often ship the Python header files as
88a separate package, called *python-dev* or *python-devel*.
89
90Install headers on Debian/Ubuntu:
91
92* ``sudo apt-get install python-dev``
93
94Windows Notes
95-------------
96
97* `Binary installers <http://www.bytereef.org/mpdecimal/download.html>`_
98
99Links
100-----
101
102* `cdecimal project homepage <http://www.bytereef.org/mpdecimal/index.html>`_
103* `cdecimal benchmarks <http://www.bytereef.org/mpdecimal/benchmarks.html>`_
104
105"""
106
107
108PY_MAJOR = sys.version_info[0]
109PY_MINOR = sys.version_info[1]
110ARCH = platform.architecture()[0]
111SYSTEM = sys.platform
112MACHINE = None
113
114def err_exit(mesg):
115    sys.stderr.write("setup.py: error: %s\n" % mesg)
116    sys.exit(1)
117
118def uninstall():
119    def check_file(name, again=False):
120        inputfunc = input if PY_MAJOR >= 3 else raw_input
121        if re.search('cdecimal.*\.(so|pyd|egg)', name):
122            if again:
123                ans = inputfunc("Please answer 'y' or 'n': ")
124            else:
125                print("Previously installed version detected: %s" % name)
126                ans = inputfunc("Remove it [y/n]? ")
127            ans = ans.lower()
128            if ans == 'y':
129                os.unlink(name)
130            elif ans != 'n':
131                check_file(name, True)
132    for p in sys.path:
133        if os.path.isdir(p):
134            for item in os.listdir(p):
135                f = os.path.join(p, item)
136                check_file(f)
137        elif os.path.isfile(p):
138            check_file(p)
139
140
141if not ((PY_MAJOR == 2 and PY_MINOR >= 5) or PY_MAJOR >= 3):
142    err_exit("Python version >= 2.5 required.")
143if not (ARCH == '64bit' or ARCH == '32bit'):
144    err_exit("64-bit or 32-bit machine required.")
145
146if 'uninstall' in sys.argv:
147    uninstall()
148    sys.exit(0)
149
150
151define_macros = []
152undef_macros = ['NDEBUG']
153if 'build' in sys.argv or 'build_ext' in sys.argv:
154    sys.argv.append('--force')
155if '--without-threads' in sys.argv:
156    # In mpdecimal-1.2, the speed penalty for thread local contexts
157    # has been reduced to 8-16%. The default is now to enable thread
158    # local contexts. To get the old default (faster), use this option.
159    sys.argv.remove('--without-threads')
160    define_macros.append(('WITHOUT_THREADS', 1))
161else:
162    if SYSTEM == 'win32': # also valid for 64-bit.
163        try:
164            import threading
165        except ImportError:
166            define_macros.append(('WITHOUT_THREADS', 1))
167    elif not sysconfig.get_config_var('WITH_THREAD'):
168        define_macros.append(('WITHOUT_THREADS', 1))
169
170# Force specific configuration:
171c = ('x64', 'uint128', 'ansi64', 'ppro', 'ansi32', 'ansi-legacy', 'universal')
172for i, v in enumerate(sys.argv[:]):
173    if '--with-machine=' in v:
174        MACHINE = v.split('=')[1]
175        sys.argv.pop(i)
176        break
177if MACHINE and not MACHINE in c:
178    err_exit("invalid machine: %s" % MACHINE)
179
180if not MACHINE and 'darwin' in SYSTEM:
181    # multi-arch support
182    MACHINE = 'universal'
183
184if SYSTEM == 'win32':
185    # For automated testing: remove empty string
186    sys.argv = [v for v in sys.argv if v != ""]
187
188
189#
190# Generally, it is also possible to build the static libmpdec.a and
191# use that to build the Python module. There are systems where this
192# approach leads to problems:
193#
194# Windows: Changes made by Python's setlocale() only affect the
195#          state of the dynamically linked CRT. This means that
196#          some features of the format() function will not work
197#          properly.
198#
199# AIX:     If built with the static library, the gcc-internals
200#          __multi3 and __udivti3 are undefined.
201#
202MULTILIB_ERR_64 = """
203##
204## Error: 64-bit Python detected, but ./configure generates 32-bit config.
205##
206## One of the following choices must be passed to setup.py:
207##
208##    --with-machine={x64,uint128,ansi64}
209##
210## OS X only, universal build:
211##
212##    --with-machine=universal
213##
214"""
215MULTILIB_ERR_32 = """
216##
217## Error: 32-bit Python detected, but ./configure generates 64-bit config.
218##
219## One of the following choices must be passed to setup.py:
220##
221##    --with-machine={ppro,ansi32}
222##
223## OS X only, universal build:
224##
225##    --with-machine=universal
226##
227"""
228
229def configure(machine, cc, py_size_t):
230    os.chmod("./configure", 0x1ed) # pip removes execute permissions.
231    if machine: # string has been validated.
232        os.system("./configure MACHINE=%s" % machine)
233    elif 'sunos' in SYSTEM and py_size_t == 8:
234        # cc is from sysconfig.
235        os.system("./configure CC='%s -m64'" % cc)
236    else:
237        os.system("./configure")
238    f = open("config.h")
239    config_vars = {}
240    for line in f:
241        if line.startswith("#define"):
242            l = line.split()
243            try:
244                config_vars[l[1]] = int(l[2])
245            except ValueError:
246                pass
247        elif line.startswith("/* #undef"):
248            l = line.split()
249            config_vars[l[2]] = 0
250    f.close()
251    return config_vars
252
253def cdecimal_ext(machine):
254    depends = [
255        'basearith.h', 'bits.h', 'constants.h', 'convolute.h', 'crt.h',
256        'difradix2.h', 'docstrings.h', 'fnt.h', 'fourstep.h', 'io.h',
257        'memory.h', 'mpdecimal.h', 'mpdecimal32.h', 'mpdecimal64.h',
258        'mptypes.h', 'numbertheory.h', 'sixstep.h', 'transpose.h',
259        'typearith.h', 'umodarith.h'
260    ]
261    sources = [
262        'basearith.c', 'constants.c', 'context.c', 'convolute.c',
263        'crt.c', 'difradix2.c', 'fnt.c', 'fourstep.c', 'io.c', 'memory.c',
264        'mpdecimal.c', 'numbertheory.c', 'sixstep.c', 'transpose.c'
265    ]
266    sources.append('cdecimal2.c' if PY_MAJOR == 2 else 'cdecimal3.c')
267    extra_compile_args = []
268    extra_link_args = []
269    extra_objects = []
270
271
272    if SYSTEM == 'win32':
273        define_macros.append(('_CRT_SECURE_NO_WARNINGS', '1'))
274        if not machine: # command line option
275            if ARCH == '64bit':
276                machine = 'x64'
277            else:
278                machine = 'ppro'
279
280        if machine == 'x64':
281            if not (PY_MAJOR == 2 and PY_MINOR == 5):
282                # set the build environment for ml64
283                from distutils.msvc9compiler import MSVCCompiler
284                cc = MSVCCompiler()
285                cc.initialize()
286                del cc
287            define_macros.extend([('CONFIG_64', '1'), ('MASM', '1')])
288            extra_objects = ['vcdiv64.obj']
289            os.system("ml64 /c /Cx vcdiv64.asm")
290            copy2("mpdecimal64vc.h", "mpdecimal.h")
291        elif machine == 'ansi64':
292            define_macros.extend([('CONFIG_64', '1'), ('ANSI', '1')])
293            copy2("mpdecimal64vc.h", "mpdecimal.h")
294        elif machine == 'ppro':
295            define_macros.extend([('CONFIG_32', '1'), ('PPRO', '1'),
296                                  ('MASM', '1')])
297            copy2("mpdecimal32vc.h", "mpdecimal.h")
298        elif machine == 'ansi32':
299            define_macros.extend([('CONFIG_32', '1'), ('ANSI', '1')])
300            copy2("mpdecimal32vc.h", "mpdecimal.h")
301        elif machine == 'ansi-legacy':
302            define_macros.extend([('CONFIG_32', '1'), ('ANSI', '1'),
303                                  ('LEGACY_COMPILER', '1')])
304            copy2("mpdecimal32vc.h", "mpdecimal.h")
305        else:
306            err_exit("unsupported machine: %s" % machine)
307
308    else:
309        cc = sysconfig.get_config_var('CC')
310        py_size_t = sysconfig.get_config_var('SIZEOF_SIZE_T')
311        if py_size_t != 8 and py_size_t != 4:
312            err_exit("unsupported architecture: sizeof(size_t) must be 8 or 4")
313
314        depends.append('config.h')
315        config_vars = configure(machine, cc, py_size_t)
316        if config_vars['SIZEOF_SIZE_T'] != 8 and \
317           config_vars['SIZEOF_SIZE_T'] != 4:
318            err_exit("unsupported architecture: sizeof(size_t) must be 8 or 4")
319
320        if not machine and py_size_t != config_vars['SIZEOF_SIZE_T']:
321            if py_size_t == 8:
322                print(MULTILIB_ERR_64)
323            elif py_size_t == 4:
324                print(MULTILIB_ERR_32)
325            sys.exit(1)
326
327        if not machine:
328            if config_vars['HAVE_GCC_ASM_FOR_X64']:
329                machine = 'x64'
330            elif config_vars['HAVE_UINT128_T']:
331                machine = 'uint128'
332            elif config_vars['HAVE_GCC_ASM_FOR_X87']:
333                machine = 'ppro'
334
335        if machine == 'universal':
336            add_macros = [('UNIVERSAL', '1')]
337        elif py_size_t == 8:
338            if machine == 'x64':
339                add_macros = [('CONFIG_64', '1'), ('ASM', '1')]
340            elif machine == 'uint128':
341                add_macros = [('CONFIG_64', '1'), ('ANSI', '1'),
342                              ('HAVE_UINT128_T', '1')]
343            else:
344                add_macros = [('CONFIG_64', '1'), ('ANSI', '1')]
345        else:
346            if machine == 'ppro' and ('gcc' in cc or 'clang' in cc):
347                # suncc: register allocator cannot deal with the inline asm.
348                # icc >= 11.0 works as well.
349                add_macros = [('CONFIG_32', '1'), ('PPRO', '1'), ('ASM', '1')]
350                if config_vars['HAVE_IPA_PURE_CONST_BUG']:
351                    # Some versions of gcc miscompile inline asm:
352                    # http://gcc.gnu.org/bugzilla/show_bug.cgi?id=46491
353                    # http://gcc.gnu.org/ml/gcc/2010-11/msg00366.html
354                    extra_compile_args.extend(['-fno-ipa-pure-const'])
355            else: # ansi32
356                add_macros = [('CONFIG_32', '1'), ('ANSI', '1')]
357                if machine == 'ansi-legacy':
358                    add_macros.append(('LEGACY_COMPILER', '1'))
359
360        # Uncomment for warnings:
361        # extra_compile_args.extend(['-Wall', '-W', '-Wno-missing-field-initializers'])
362
363        # Uncomment for a debug build:
364        # extra_compile_args.extend(['-O0', '-g'])
365
366        if os.environ.get("CFLAGS"):
367            # Distutils drops -fno-strict-aliasing if CFLAGS are set:
368            # http://bugs.python.org/issue10847
369            if 'xlc' in cc:
370                extra_compile_args.append('-qnoansialias')
371            else:
372                extra_compile_args.append('-fno-strict-aliasing')
373
374        define_macros.extend(add_macros)
375
376        if config_vars['HAVE_GLIBC_MEMMOVE_BUG']:
377            # _FORTIFY_SOURCE wrappers for memmove and bcopy are incorrect:
378            # http://sourceware.org/ml/libc-alpha/2010-12/msg00009.html
379            undef_macros.append('_FORTIFY_SOURCE')
380
381    ext = Extension (
382        'cdecimal',
383        define_macros=define_macros,
384        undef_macros=undef_macros,
385        depends=depends,
386        extra_compile_args=extra_compile_args,
387        extra_link_args=extra_link_args,
388        sources=sources,
389        extra_objects=extra_objects
390    )
391    return ext
392
393
394setup (
395    name = 'cdecimal',
396    version = '2.3',
397    description = DESCRIPTION,
398    long_description = LONG_DESCRIPTION,
399    url = 'http://www.bytereef.org/mpdecimal/index.html',
400    author = 'Stefan Krah',
401    author_email = 'skrah@bytereef.org',
402    license = 'BSD License',
403    download_url = 'http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.3.tar.gz',
404    keywords = ["decimal", "floating point", "correctly-rounded",
405                "arithmetic", "arbitrary precision"],
406    platforms = ["Many"],
407    classifiers = [
408        "Development Status :: 5 - Production/Stable",
409        "Intended Audience :: Developers",
410        "Intended Audience :: Education",
411        "Intended Audience :: End Users/Desktop",
412        "Intended Audience :: Financial and Insurance Industry",
413        "Intended Audience :: Science/Research",
414        "License :: OSI Approved :: BSD License",
415        "Programming Language :: C",
416        "Programming Language :: Python :: 2.5",
417        "Programming Language :: Python :: 2.6",
418        "Programming Language :: Python :: 2.7",
419        "Programming Language :: Python :: 3",
420        "Programming Language :: Python :: 3.1",
421        "Programming Language :: Python :: 3.2",
422        "Operating System :: OS Independent",
423        "Topic :: Scientific/Engineering :: Mathematics",
424        "Topic :: Software Development"
425    ],
426    ext_modules = [cdecimal_ext(MACHINE)]
427)
428
429
430PATHLIST = glob("build/lib.*/cdecimal*")
431if PATHLIST:
432    CDECIMAL = PATHLIST[0]
433    copy2(CDECIMAL, "python")
434
435
436
437