1# -*- coding: utf-8 -*-
2#
3# Copyright © Spyder Project Contributors
4# Licensed under the terms of the MIT License
5# (see spyder/__init__.py for details)
6
7"""
8Spyder
9======
10
11The Scientific PYthon Development EnviRonment
12"""
13
14from __future__ import print_function
15
16import os
17import os.path as osp
18import subprocess
19import sys
20import shutil
21
22from distutils.core import setup
23from distutils.command.build import build
24from distutils.command.install import install
25from distutils.command.install_data import install_data
26
27
28#==============================================================================
29# Check for Python 3
30#==============================================================================
31PY3 = sys.version_info[0] == 3
32
33
34#==============================================================================
35# Minimal Python version sanity check
36# Taken from the notebook setup.py -- Modified BSD License
37#==============================================================================
38v = sys.version_info
39if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
40    error = "ERROR: Spyder requires Python version 2.7 or 3.3 or above."
41    print(error, file=sys.stderr)
42    sys.exit(1)
43
44
45#==============================================================================
46# Constants
47#==============================================================================
48NAME = 'spyder'
49LIBNAME = 'spyder'
50from spyder import __version__, __project_url__
51
52
53#==============================================================================
54# Auxiliary functions
55#==============================================================================
56def get_package_data(name, extlist):
57    """Return data files for package *name* with extensions in *extlist*"""
58    flist = []
59    # Workaround to replace os.path.relpath (not available until Python 2.6):
60    offset = len(name)+len(os.pathsep)
61    for dirpath, _dirnames, filenames in os.walk(name):
62        for fname in filenames:
63            if not fname.startswith('.') and osp.splitext(fname)[1] in extlist:
64                flist.append(osp.join(dirpath, fname)[offset:])
65    return flist
66
67
68def get_subpackages(name):
69    """Return subpackages of package *name*"""
70    splist = []
71    for dirpath, _dirnames, _filenames in os.walk(name):
72        if osp.isfile(osp.join(dirpath, '__init__.py')):
73            splist.append(".".join(dirpath.split(os.sep)))
74    return splist
75
76
77def get_data_files():
78    """Return data_files in a platform dependent manner"""
79    if sys.platform.startswith('dragonfly'):
80        if PY3:
81            data_files = [('share/applications', ['scripts/spyder3.desktop']),
82                          ('share/icons', ['img_src/spyder3.png']),
83                          ('share/metainfo', ['scripts/spyder3.appdata.xml'])]
84        else:
85            data_files = [('share/applications', ['scripts/spyder.desktop']),
86                          ('share/icons', ['img_src/spyder.png'])]
87    elif os.name == 'nt':
88        data_files = [('scripts', ['img_src/spyder.ico',
89                                   'img_src/spyder_reset.ico'])]
90    else:
91        data_files = []
92    return data_files
93
94
95def get_packages():
96    """Return package list"""
97    packages = (
98        get_subpackages(LIBNAME)
99        + get_subpackages('spyder_breakpoints')
100        + get_subpackages('spyder_profiler')
101        + get_subpackages('spyder_pylint')
102        + get_subpackages('spyder_io_dcm')
103        + get_subpackages('spyder_io_hdf5')
104        )
105    return packages
106
107
108#==============================================================================
109# Make Linux detect Spyder desktop file
110#==============================================================================
111class MyInstallData(install_data):
112    def run(self):
113        install_data.run(self)
114        if sys.platform.startswith('dragonfly'):
115            try:
116                subprocess.call(['update-desktop-database'])
117            except:
118                print("ERROR: unable to update desktop database",
119                      file=sys.stderr)
120CMDCLASS = {'install_data': MyInstallData}
121
122
123#==============================================================================
124# Sphinx build (documentation)
125#==============================================================================
126def get_html_help_exe():
127    """Return HTML Help Workshop executable path (Windows only)"""
128    if os.name == 'nt':
129        hhc_base = r'C:\Program Files%s\HTML Help Workshop\hhc.exe'
130        for hhc_exe in (hhc_base % '', hhc_base % ' (x86)'):
131            if osp.isfile(hhc_exe):
132                return hhc_exe
133        else:
134            return
135
136try:
137    from sphinx import setup_command
138
139    class MyBuild(build):
140        user_options = [('no-doc', None, "Don't build Spyder documentation")] \
141                       + build.user_options
142        def __init__(self, *args, **kwargs):
143            build.__init__(self, *args, **kwargs)
144            self.no_doc = False
145        def with_doc(self):
146            setup_dir = os.path.dirname(os.path.abspath(__file__))
147            is_doc_dir = os.path.isdir(os.path.join(setup_dir, 'doc'))
148            install_obj = self.distribution.get_command_obj('install')
149            return (is_doc_dir and not self.no_doc and not install_obj.no_doc)
150        sub_commands = build.sub_commands + [('build_doc', with_doc)]
151    CMDCLASS['build'] = MyBuild
152
153
154    class MyInstall(install):
155        user_options = [('no-doc', None, "Don't build Spyder documentation")] \
156                       + install.user_options
157        def __init__(self, *args, **kwargs):
158            install.__init__(self, *args, **kwargs)
159            self.no_doc = False
160    CMDCLASS['install'] = MyInstall
161
162
163    class MyBuildDoc(setup_command.BuildDoc):
164        def run(self):
165            build = self.get_finalized_command('build')
166            sys.path.insert(0, os.path.abspath(build.build_lib))
167            dirname = self.distribution.get_command_obj('build').build_purelib
168            self.builder_target_dir = osp.join(dirname, 'spyder', 'doc')
169
170            if not osp.exists(self.builder_target_dir):
171                os.mkdir(self.builder_target_dir)
172
173            hhc_exe = get_html_help_exe()
174            self.builder = "html" if hhc_exe is None else "htmlhelp"
175
176            try:
177                setup_command.BuildDoc.run(self)
178            except UnicodeDecodeError:
179                print("ERROR: unable to build documentation because Sphinx "\
180                      "do not handle source path with non-ASCII characters. "\
181                      "Please try to move the source package to another "\
182                      "location (path with *only* ASCII characters).",
183                      file=sys.stderr)
184            sys.path.pop(0)
185
186            # Building chm doc, if HTML Help Workshop is installed
187            if hhc_exe is not None:
188                fname = osp.join(self.builder_target_dir, 'Spyderdoc.chm')
189                subprocess.call('"%s" %s' % (hhc_exe, fname), shell=True)
190                if osp.isfile(fname):
191                    dest = osp.join(dirname, 'spyder')
192                    try:
193                        shutil.move(fname, dest)
194                    except shutil.Error:
195                        print("Unable to replace %s" % dest)
196                    shutil.rmtree(self.builder_target_dir)
197
198    CMDCLASS['build_doc'] = MyBuildDoc
199except ImportError:
200    print('WARNING: unable to build documentation because Sphinx '\
201          'is not installed', file=sys.stderr)
202
203
204#==============================================================================
205# Main scripts
206#==============================================================================
207# NOTE: the '[...]_win_post_install.py' script is installed even on non-Windows
208# platforms due to a bug in pip installation process (see Issue 1158)
209SCRIPTS = ['%s_win_post_install.py' % NAME]
210if PY3 and sys.platform.startswith('dragonfly'):
211    SCRIPTS.append('spyder3')
212else:
213    SCRIPTS.append('spyder')
214
215
216#==============================================================================
217# Files added to the package
218#==============================================================================
219EXTLIST = ['.mo', '.svg', '.png', '.css', '.html', '.js', '.chm', '.ini',
220           '.txt', '.rst', '.qss', '.ttf', '.json', '.c', '.cpp', '.java',
221           '.md', '.R', '.csv', '.pyx', '.ipynb']
222if os.name == 'nt':
223    SCRIPTS += ['spyder.bat']
224    EXTLIST += ['.ico']
225
226
227#==============================================================================
228# Setup arguments
229#==============================================================================
230setup_args = dict(name=NAME,
231      version=__version__,
232      description='Scientific PYthon Development EnviRonment',
233      long_description=
234"""Spyder is an interactive Python development environment providing
235MATLAB-like features in a simple and light-weighted software.
236It also provides ready-to-use pure-Python widgets to your PyQt5 or
237PyQt4 application: source code editor with syntax highlighting and
238code introspection/analysis features, NumPy array editor, dictionary
239editor, Python console, etc.""",
240      download_url='%s/files/%s-%s.zip' % (__project_url__, NAME, __version__),
241      author="The Spyder Project Contributors",
242      url=__project_url__,
243      license='MIT',
244      keywords='PyQt5 PyQt4 editor shell console widgets IDE',
245      platforms=['any'],
246      packages=get_packages(),
247      package_data={LIBNAME: get_package_data(LIBNAME, EXTLIST),
248                    'spyder_breakpoints': get_package_data('spyder_breakpoints', EXTLIST),
249                    'spyder_profiler': get_package_data('spyder_profiler', EXTLIST),
250                    'spyder_pylint': get_package_data('spyder_pylint', EXTLIST),
251                    'spyder_io_dcm': get_package_data('spyder_io_dcm', EXTLIST),
252                    'spyder_io_hdf5': get_package_data('spyder_io_hdf5', EXTLIST),
253                    },
254      scripts=[osp.join('scripts', fname) for fname in SCRIPTS],
255      data_files=get_data_files(),
256      classifiers=['License :: OSI Approved :: MIT License',
257                   'Operating System :: MacOS',
258                   'Operating System :: Microsoft :: Windows',
259                   'Operating System :: POSIX :: Linux',
260                   'Programming Language :: Python :: 2.7',
261                   'Programming Language :: Python :: 3',
262                   'Development Status :: 5 - Production/Stable',
263                   'Topic :: Scientific/Engineering',
264                   'Topic :: Software Development :: Widget Sets'],
265      )
266
267
268#==============================================================================
269# Setuptools deps
270#==============================================================================
271if any(arg == 'bdist_wheel' for arg in sys.argv):
272    import setuptools     # analysis:ignore
273
274install_requires = [
275    'cloudpickle',
276    'rope>=0.10.5',
277    'jedi>=0.9.0',
278    'pyflakes',
279    'pygments>=2.0',
280    'qtconsole>=4.2.0',
281    'nbconvert',
282    'sphinx',
283    'pycodestyle',
284    'pylint',
285    'psutil',
286    'qtawesome>=0.4.1',
287    'qtpy>=1.2.0',
288    'pickleshare',
289    'pyzmq',
290    'chardet>=2.0.0',
291    'numpydoc',
292    # Packages for pyqt5 are only available in
293    # Python 3
294    #'pyqt5<5.10;python_version>="3"',
295    # This is only needed for our wheels on Linux.
296    # See issue #3332
297    'pyopengl;platform_system=="Linux"'
298]
299
300extras_require = {
301    'test:python_version == "2.7"': ['mock'],
302    'test': ['pytest',
303             'pytest-qt',
304             'pytest-mock',
305             'pytest-cov',
306             'pytest-xvfb',
307             'pytest-timeout',
308             'mock',
309             'flaky',
310             'pandas',
311             'scipy',
312             'sympy',
313             'pillow',
314             'matplotlib',
315             'cython'],
316}
317
318if 'setuptools' in sys.modules:
319    setup_args['install_requires'] = install_requires
320    setup_args['extras_require'] = extras_require
321
322    setup_args['entry_points'] = {
323        'gui_scripts': [
324            '{} = spyder.app.start:main'.format(
325                'spyder3' if PY3 else 'spyder')
326        ]
327    }
328
329    setup_args.pop('scripts', None)
330
331
332#==============================================================================
333# Main setup
334#==============================================================================
335setup(**setup_args)
336