1#!/usr/bin/env python
2
3#-----------------------------------------------------------------------------
4# Copyright (c) 2013-2015, PyStan developers
5#
6# This file is licensed under Version 3.0 of the GNU General Public
7# License. See LICENSE for a text of the license.
8#-----------------------------------------------------------------------------
9
10#-----------------------------------------------------------------------------
11# This file is part of PyStan.
12#
13# PyStan is free software: you can redistribute it and/or modify it
14# under the terms of the GNU General Public License version 3 as
15# published by the Free Software Foundation.
16#
17# PyStan is distributed in the hope that it will be useful, but
18# WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20# General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with PyStan.  If not, see <http://www.gnu.org/licenses/>.
24#-----------------------------------------------------------------------------
25import ast
26import codecs
27import os
28import platform
29import sys
30
31LONG_DESCRIPTION = codecs.open('README.rst', encoding='utf-8').read()
32NAME         = 'pystan'
33DESCRIPTION  = 'Python interface to Stan, a package for Bayesian inference'
34AUTHOR       = 'PyStan Developers'
35AUTHOR_EMAIL = 'stan-users@googlegroups.com'
36URL          = 'https://github.com/stan-dev/pystan'
37LICENSE      = 'GPLv3'
38CLASSIFIERS = [
39    'Programming Language :: Python',
40    'Programming Language :: Python :: 2',
41    'Programming Language :: Python :: 3',
42    'Programming Language :: Cython',
43    'Development Status :: 4 - Beta',
44    'Environment :: Console',
45    'Operating System :: OS Independent',
46    'Intended Audience :: Developers',
47    'Intended Audience :: Science/Research',
48    'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
49    'Topic :: Scientific/Engineering',
50    'Topic :: Scientific/Engineering :: Information Analysis'
51]
52
53
54# VersionFinder from from django-compressor
55class VersionFinder(ast.NodeVisitor):
56    def __init__(self):
57        self.version = None
58
59    def visit_Assign(self, node):
60        if node.targets[0].id == '__version__':
61            self.version = node.value.s
62
63
64def read(*parts):
65    filename = os.path.join(os.path.dirname(__file__), *parts)
66    with codecs.open(filename, encoding='utf-8') as fp:
67        return fp.read()
68
69
70def find_version(*parts):
71    finder = VersionFinder()
72    finder.visit(ast.parse(read(*parts)))
73    return finder.version
74
75
76###############################################################################
77# Optional setuptools features
78# We need to import setuptools early, if we want setuptools features,
79# as it monkey-patches the 'setup' function
80
81# For some commands, use setuptools
82if len(set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
83           'bdist_wininst', 'install_egg_info', 'build_sphinx',
84           'egg_info', 'easy_install', 'upload', 'bdist_wheel',
85           '--single-version-externally-managed',
86            )).intersection(sys.argv)) > 0:
87    import setuptools
88    extra_setuptools_args = dict(
89        install_requires=['Cython>=0.22,!=0.25.1', 'numpy >= 1.7'],
90        zip_safe=False, # the package can run out of an .egg file
91        include_package_data=True,
92    )
93else:
94    extra_setuptools_args = dict()
95
96###############################################################################
97
98from distutils.errors import CCompilerError, DistutilsError
99from distutils.extension import Extension
100
101stan_include_dirs = ['pystan/stan/src',
102                     'pystan/stan/lib/stan_math/',
103                     'pystan/stan/lib/stan_math/lib/eigen_3.3.3',
104                     'pystan/stan/lib/stan_math/lib/boost_1.69.0',
105                     'pystan/stan/lib/stan_math/lib/sundials_4.1.0/include']
106stan_macros = [
107    ('BOOST_DISABLE_ASSERTS', None),
108    ('BOOST_NO_DECLTYPE', None),
109    ('BOOST_PHOENIX_NO_VARIADIC_EXPRESSION', None),  # needed for stanc
110    ('BOOST_RESULT_OF_USE_TR1', None),
111    ('FUSION_MAX_VECTOR_SIZE', 12),  # for parser, stan-dev/pystan#222
112]
113extra_compile_args = [
114    '-Os',
115    '-ftemplate-depth-256',
116    '-Wno-unused-function',
117    '-Wno-uninitialized',
118    '-std=c++1y',
119]
120
121if platform.platform().startswith('Win'):
122    from Cython.Build.Inline import _get_build_extension
123    if _get_build_extension().compiler in (None, 'msvc'):
124        print("Warning: MSVC is not supported")
125        extra_compile_args = [
126            '/EHsc',
127            '-DBOOST_DATE_TIME_NO_LIB',
128            '/std:c++14',
129        ]
130    else:
131        # fix bug in MingW-W64
132        # use posix threads
133        extra_compile_args.extend([
134            "-D_hypot=hypot",
135            "-pthread",
136            "-fexceptions",
137            ])
138
139
140stanc_sources = [
141    "pystan/stan/src/stan/lang/ast_def.cpp",
142    "pystan/stan/src/stan/lang/grammars/bare_type_grammar_inst.cpp",
143    "pystan/stan/src/stan/lang/grammars/block_var_decls_grammar_inst.cpp",
144    "pystan/stan/src/stan/lang/grammars/expression07_grammar_inst.cpp",
145    "pystan/stan/src/stan/lang/grammars/expression_grammar_inst.cpp",
146    "pystan/stan/src/stan/lang/grammars/functions_grammar_inst.cpp",
147    "pystan/stan/src/stan/lang/grammars/indexes_grammar_inst.cpp",
148    "pystan/stan/src/stan/lang/grammars/local_var_decls_grammar_inst.cpp",
149    "pystan/stan/src/stan/lang/grammars/program_grammar_inst.cpp",
150    "pystan/stan/src/stan/lang/grammars/semantic_actions_def.cpp",
151    "pystan/stan/src/stan/lang/grammars/statement_2_grammar_inst.cpp",
152    "pystan/stan/src/stan/lang/grammars/statement_grammar_inst.cpp",
153    "pystan/stan/src/stan/lang/grammars/term_grammar_inst.cpp",
154    "pystan/stan/src/stan/lang/grammars/whitespace_grammar_inst.cpp",
155]
156
157extensions = [
158    Extension("pystan._api",
159              ["pystan/_api.pyx"] + stanc_sources,
160              language='c++',
161              define_macros=stan_macros,
162              include_dirs=stan_include_dirs,
163              extra_compile_args=extra_compile_args),
164    Extension("pystan._chains",
165              ["pystan/_chains.pyx"],
166              language='c++',
167              define_macros=stan_macros,
168              include_dirs=stan_include_dirs,
169              extra_compile_args=extra_compile_args),
170    # _misc.pyx does not use Stan libs
171    Extension("pystan._misc",
172              ["pystan/_misc.pyx"],
173              language='c++',
174              extra_compile_args=extra_compile_args)
175]
176
177
178## package data
179package_data_pats = ['*.hpp', '*.pxd', '*.pyx', 'tests/data/*.csv',
180                     'tests/data/*.stan', 'lookuptable/*.txt']
181
182# get every file under pystan/stan/src and pystan/stan/lib
183stan_files_all = sum(
184    [[os.path.join(path.replace('pystan/', ''), fn) for fn in files]
185     for path, dirs, files in os.walk('pystan/stan/src/')], [])
186
187lib_files_all = sum(
188    [[os.path.join(path.replace('pystan/', ''), fn) for fn in files]
189     for path, dirs, files in os.walk('pystan/stan/lib/')], [])
190
191package_data_pats += stan_files_all
192package_data_pats += lib_files_all
193
194
195def setup_package():
196    metadata = dict(name=NAME,
197                    version=find_version("pystan", "__init__.py"),
198                    maintainer=AUTHOR,
199                    maintainer_email=AUTHOR_EMAIL,
200                    packages=['pystan',
201                              'pystan.tests',
202                              'pystan.experimental',
203                              'pystan.external',
204                              'pystan.external.pymc',
205                              'pystan.external.enum',
206                              'pystan.external.scipy'],
207                    ext_modules=extensions,
208                    package_data={'pystan': package_data_pats},
209                    platforms='any',
210                    description=DESCRIPTION,
211                    license=LICENSE,
212                    url=URL,
213                    long_description=LONG_DESCRIPTION,
214                    classifiers=CLASSIFIERS,
215                    **extra_setuptools_args)
216    if len(sys.argv) >= 2 and ('--help' in sys.argv[1:] or sys.argv[1]
217                               in ('--help-commands', 'egg_info', '--version', 'clean')):
218        # For these actions, neither Numpy nor Cython is required.
219        #
220        # They are required to succeed when pip is used to install PyStan
221        # when, for example, Numpy is not yet present.
222        try:
223            from setuptools import setup
224        except ImportError:
225            from distutils.core import setup
226        dist = setup(**metadata)
227    else:
228        import distutils.core
229        distutils.core._setup_stop_after = 'commandline'
230        from distutils.core import setup
231        try:
232            from Cython.Build import cythonize
233            # FIXME: if header only works, no need for numpy.distutils at all
234            from numpy.distutils.command import install
235        except ImportError:
236            raise SystemExit("Cython>=0.22 and NumPy are required.")
237
238        metadata['ext_modules'] = cythonize(extensions)
239        dist = setup(**metadata)
240
241        metadata['cmdclass'] = {'install': install.install}
242    try:
243        dist.run_commands()
244    except KeyboardInterrupt:
245        raise SystemExit("Interrupted")
246    except (IOError, os.error) as exc:
247        from distutils.util import grok_environment_error
248        error = grok_environment_error(exc)
249    except (DistutilsError, CCompilerError) as msg:
250            raise SystemExit("error: " + str(msg))
251
252
253if __name__ == '__main__':
254    setup_package()
255