1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4import hashlib
5import io
6import os
7import re
8import shutil
9import subprocess
10import sys
11import warnings
12from setuptools import setup, Extension
13
14
15pkg_name = 'symcxx'
16
17SYMCXX_RELEASE_VERSION = os.environ.get('SYMCXX_RELEASE_VERSION', '')  # v*
18
19# Cythonize .pyx file if it exists (not in source distribution)
20ext_modules = []
21
22
23def _read(path, macro='SYMCXX_TYPE', inc_dir='./'):
24    for l in io.open(inc_dir + path, 'rt', encoding='utf-8'):
25        if l.startswith('#include'):
26            inner_path = l.split('#include')[1]
27            inner_path = inner_path.strip().strip('<').strip('>').strip('"')
28            for inner_l in _read(inner_path, macro, inc_dir):
29                yield inner_l
30        if not l.startswith(macro + '('):
31            continue
32        l = l.split('//')[0]  # strip comments marked with // (misses /* */)
33        l = ')'.join(l.split(macro + '(')[1].split(')')[:-1])
34        yield tuple([_.strip() for _ in l.split(',')])
35
36USE_CYTHON = None
37
38if len(sys.argv) > 1 and '--help' not in sys.argv[1:] and sys.argv[1] not in (
39        '--help-commands', 'egg_info', 'clean', '--version'):
40    try:
41        from mako.template import Template
42        from mako.exceptions import text_error_template
43        from Cython.Build import cythonize
44    except ImportError:
45        USE_CYTHON = False
46    else:
47        pyx_path = 'symcxx/_symcxx.pyx'
48        template_path = pyx_path + '.mako_template'
49        USE_CYTHON = os.path.exists(template_path)
50
51    ext = '.pyx' if USE_CYTHON else '.cpp'
52    ext_modules = [Extension(
53        'symcxx._symcxx',
54        ['symcxx/_symcxx'+ext]
55    )]
56    if USE_CYTHON:
57        stub = 'types_nonatomic_'
58        path_stub = 'symcxx/' + stub
59        subsd = {}
60        subsd['types'] = list(_read('symcxx/types_atomic.inc', inc_dir='./include/'))
61        for k in ('unary', 'binary', 'args_stack'):
62            subsd[stub+k] = list(_read(path_stub+k+'.inc', inc_dir='./include/'))
63            subsd['types'] += subsd[stub+k]
64
65        subsd['_message_for_rendered'] = 'THIS IS A GENERATED FILE DO NOT EDIT'
66        try:
67            rendered_pyx = Template(io.open(template_path, 'rt', encoding='utf-8').read()).render(**subsd)
68        except:
69            print(text_error_template().render_unicode())
70            raise
71        else:
72            sha256hex = hashlib.sha256(rendered_pyx.encode('utf-8')).hexdigest()
73            hash_path = os.path.join('build', pyx_path.replace('/', '__')+'.sha256hex')
74            if os.path.exists(hash_path) and open(hash_path, 'rt').read(256//4) == sha256hex:
75                pass
76            else:
77                open(pyx_path, 'wt').write(rendered_pyx)
78                if not os.path.exists('build'):
79                    os.makedirs('build')
80                open(hash_path, 'wt').write(sha256hex)
81        ext_modules = cythonize(ext_modules,
82                                include_path=['./include'],
83                                gdb_debug=True)
84    else:
85        ext_modules[0].sources = [
86            'src/basic.cpp', 'src/namespace.cpp'
87        ] + ext_modules[0].sources
88    ext_modules[0].include_dirs += ['./include']
89    ext_modules[0].language = 'c++'
90    ext_modules[0].extra_compile_args = ['-std=c++11']
91
92
93# http://conda.pydata.org/docs/build.html#environment-variables-set-during-the-build-process
94if os.environ.get('CONDA_BUILD', '0') == '1':
95    try:
96        SYMCXX_RELEASE_VERSION = 'v' + open(
97            '__conda_version__.txt', 'rt').readline().rstrip()
98    except IOError:
99        pass
100
101release_py_path = os.path.join(pkg_name, '_release.py')
102
103if len(SYMCXX_RELEASE_VERSION) > 0:
104    if SYMCXX_RELEASE_VERSION[0] == 'v':
105        TAGGED_RELEASE = True
106        __version__ = SYMCXX_RELEASE_VERSION[1:]
107    else:
108        raise ValueError("Ill formated version")
109else:
110    TAGGED_RELEASE = False
111    # read __version__ attribute from _release.py:
112    exec(open(release_py_path).read())
113    if __version__.endswith('git'):
114        try:
115            _git_version = subprocess.check_output(
116                ['git', 'describe', '--dirty']).rstrip().decode('utf-8').replace('-dirty', '.dirty')
117        except subprocess.CalledProcessError:
118            warnings.warn("A git-archive is being installed - version information incomplete.")
119        else:
120            if 'develop' not in sys.argv:
121                warnings.warn("Using git to derive version: dev-branches may compete.")
122                __version__ = re.sub('v([0-9.]+)-(\d+)-(\w+)', r'\1.post\2+\3', _git_version)  # .dev < '' < .post
123
124
125classifiers = [
126    "Development Status :: 3 - Alpha",
127    'License :: OSI Approved :: BSD License',
128    'Operating System :: OS Independent',
129    'Topic :: Scientific/Engineering',
130    'Topic :: Scientific/Engineering :: Mathematics',
131]
132
133tests = [
134    'symcxx.tests',
135]
136
137long_description = io.open('README.rst', encoding='utf-8').read()
138with io.open(os.path.join(pkg_name, '__init__.py'), 'rt', encoding='utf-8') as f:
139    short_description = f.read().split('"""')[1]
140
141setup_kwargs = dict(
142    name=pkg_name,
143    version=__version__,
144    description=short_description,
145    long_description=long_description,
146    classifiers=classifiers,
147    author='Björn Dahlgren',
148    author_email='bjodah@DELETEMEgmail.com',
149    url='https://github.com/bjodah/' + pkg_name,
150    license='BSD',
151    packages=[pkg_name] + tests,
152    setup_requires=['mako'] if USE_CYTHON else [],
153    install_requires=['numpy'],
154    ext_modules=ext_modules,
155)
156
157if __name__ == '__main__':
158    try:
159        if TAGGED_RELEASE:
160            # Same commit should generate different sdist
161            # depending on tagged version (set $SYMCXX_RELEASE_VERSION)
162            # e.g.:  $ SYMCXX_RELEASE_VERSION=v1.2.3 python setup.py sdist
163            # this will ensure source distributions contain the correct version
164            shutil.move(release_py_path, release_py_path+'__temp__')
165            open(release_py_path, 'wt').write(
166                "__version__ = '{}'\n".format(__version__))
167        setup(**setup_kwargs)
168    finally:
169        if TAGGED_RELEASE:
170            shutil.move(release_py_path+'__temp__', release_py_path)
171