setup.py 12.1 KB
Newer Older
payno's avatar
payno committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/python
# coding: utf8
# /*##########################################################################
#
# Copyright (c) 2015-2019 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/

__authors__ = ["Jérôme Kieffer", "Thomas Vincent", "H. Payno"]
__date__ = "12/02/2019"
__license__ = "MIT"


import sys
import os
import platform
import logging
import io

logging.basicConfig(level=logging.INFO)

logger = logging.getLogger("tomoscan.setup")


from distutils.command.build import build as _build
payno's avatar
payno committed
44

payno's avatar
payno committed
45
46
47
48
try:
    from setuptools import Command
    from setuptools.command.build_ext import build_ext
    from setuptools.command.sdist import sdist
payno's avatar
payno committed
49

payno's avatar
payno committed
50
51
52
53
54
55
56
57
    logger.info("Use setuptools")
except ImportError:
    try:
        from numpy.distutils.core import Command
    except ImportError:
        from distutils.core import Command
    from distutils.command.build_ext import build_ext
    from distutils.command.sdist import sdist
payno's avatar
payno committed
58

payno's avatar
payno committed
59
60
61
62
63
    logger.info("Use distutils")

try:
    import sphinx
    import sphinx.util.console
payno's avatar
payno committed
64

payno's avatar
payno committed
65
66
67
68
69
70
71
72
73
    sphinx.util.console.color_terminal = lambda: False
    from sphinx.setup_command import BuildDoc
except ImportError:
    sphinx = None


PROJECT = "tomoscan"

if "LANG" not in os.environ and sys.platform == "darwin" and sys.version_info[0] > 2:
payno's avatar
payno committed
74
75
    print(
        """WARNING: the LANG environment variable is not defined,
payno's avatar
payno committed
76
77
78
an utf-8 LANG is mandatory to use setup.py, you may face unexpected UnicodeError.
export LANG=en_US.utf-8
export LC_ALL=en_US.utf-8
payno's avatar
payno committed
79
80
"""
    )
payno's avatar
payno committed
81
82
83
84
85
86
87


def get_version():
    """Returns current version number from version.py file"""
    dirname = os.path.dirname(os.path.abspath(__file__))
    sys.path.insert(0, dirname)
    import tomoscan.version
payno's avatar
payno committed
88

payno's avatar
payno committed
89
90
91
92
93
94
95
96
97
98
99
100
101
    sys.path = sys.path[1:]
    return tomoscan.version.strictversion


def get_readme():
    """Returns content of README.rst file"""
    dirname = os.path.dirname(os.path.abspath(__file__))
    filename = os.path.join(dirname, "README.rst")
    with io.open(filename, "r", encoding="utf-8") as fp:
        long_description = fp.read()
    return long_description


payno's avatar
payno committed
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
classifiers = [
    "Development Status :: 4 - Beta",
    "Environment :: Console",
    "Environment :: MacOS X",
    "Environment :: Win32 (MS Windows)",
    "Intended Audience :: Education",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: MIT License",
    "Natural Language :: English",
    "Operating System :: MacOS",
    "Operating System :: Microsoft :: Windows",
    "Operating System :: POSIX",
    "Programming Language :: Python :: 3.6",
    "Programming Language :: Python :: 3.7",
    "Topic :: Scientific/Engineering :: Physics",
    "Topic :: Software Development :: Libraries :: Python Modules",
]
payno's avatar
payno committed
119
120
121
122
123

########
# Test #
########

payno's avatar
payno committed
124

payno's avatar
payno committed
125
126
class PyTest(Command):
    """Command to start tests running the script: run_tests.py"""
payno's avatar
payno committed
127

payno's avatar
payno committed
128
129
130
131
132
133
134
135
136
137
138
139
    user_options = []

    description = "Execute the unittests"

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        import subprocess
payno's avatar
payno committed
140
141

        errno = subprocess.call([sys.executable, "run_tests.py"])
payno's avatar
payno committed
142
143
144
145
146
147
148
149
150
        if errno != 0:
            raise SystemExit(errno)


# ################### #
# build_doc command   #
# ################### #

if sphinx is None:
payno's avatar
payno committed
151

payno's avatar
payno committed
152
153
    class SphinxExpectedCommand(Command):
        """Command to inform that sphinx is missing"""
payno's avatar
payno committed
154

payno's avatar
payno committed
155
156
157
158
159
160
161
162
163
164
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            raise RuntimeError(
payno's avatar
payno committed
165
166
167
                "Sphinx is required to build or test the documentation.\n"
                "Please install Sphinx (http://www.sphinx-doc.org)."
            )
payno's avatar
payno committed
168
169
170


if sphinx is not None:
payno's avatar
payno committed
171

payno's avatar
payno committed
172
173
174
175
176
177
178
179
180
181
182
    class BuildDocCommand(BuildDoc):
        """Command to build documentation using sphinx.

        Project should have already be built.
        """

        def run(self):
            # make sure the python path is pointing to the newly built
            # code so that the documentation is built on this and not a
            # previously installed version

payno's avatar
payno committed
183
            build = self.get_finalized_command("build")
payno's avatar
payno committed
184
185
186
            sys.path.insert(0, os.path.abspath(build.build_lib))

            # Build the Users Guide in HTML and TeX format
payno's avatar
payno committed
187
            for builder in ["html", "latex"]:
payno's avatar
payno committed
188
189
190
191
192
193
                self.builder = builder
                self.builder_target_dir = os.path.join(self.build_dir, builder)
                self.mkpath(self.builder_target_dir)
                BuildDoc.run(self)
            sys.path.pop(0)

payno's avatar
payno committed
194

payno's avatar
payno committed
195
196
197
198
199
200
201
202
else:
    BuildDocCommand = SphinxExpectedCommand


# ############################# #
# numpy.distutils Configuration #
# ############################# #

payno's avatar
payno committed
203
204

def configuration(parent_package="", top_path=None):
payno's avatar
payno committed
205
206
207
208
209
210
211
212
213
    """Recursive construction of package info to be used in setup().

    See http://docs.scipy.org/doc/numpy/reference/distutils.html#numpy.distutils.misc_util.Configuration
    """
    try:
        from numpy.distutils.misc_util import Configuration
    except ImportError:
        raise ImportError(
            "To install this package, you must install numpy first\n"
payno's avatar
payno committed
214
215
            "(See https://pypi.python.org/pypi/numpy)"
        )
payno's avatar
payno committed
216
217
218
219
220
    config = Configuration(None, parent_package, top_path)
    config.set_options(
        ignore_setup_xxx_py=True,
        assume_default_configuration=True,
        delegate_options_to_subpackages=True,
payno's avatar
payno committed
221
222
        quiet=True,
    )
payno's avatar
payno committed
223
224
225
    config.add_subpackage(PROJECT)
    return config

payno's avatar
payno committed
226

payno's avatar
payno committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# ############## #
# Compiler flags #
# ############## #


class Build(_build):
    """Command to support more user options for the build."""

    def _parse_env_as_bool(self, key):
        content = os.environ.get(key, "")
        value = content.lower()
        if value in ["1", "true", "yes", "y"]:
            return True
        if value in ["0", "false", "no", "n"]:
            return False
        if value in ["none", ""]:
            return None
        msg = "Env variable '%s' contains '%s'. But a boolean or an empty \
            string was expected. Variable ignored."
        logger.warning(msg, key, content)
        return None

payno's avatar
payno committed
249

payno's avatar
payno committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
################################################################################
# Debian source tree
################################################################################


class sdist_debian(sdist):
    """
    Tailor made sdist for debian
    * remove auto-generated doc
    * remove cython generated .c files
    * remove cython generated .cpp files
    * remove .bat files
    * include .l man files
    """

    description = "Create a source distribution for Debian (tarball, zip file, etc.)"

    @staticmethod
    def get_debian_name():
payno's avatar
payno committed
269
        import tomoscan.version
payno's avatar
payno committed
270

payno's avatar
payno committed
271
        name = "%s_%s" % (PROJECT, tomoscan.version.debianversion)
payno's avatar
payno committed
272
273
274
275
276
277
278
279
280
281
282
283
284
        return name

    def prune_file_list(self):
        sdist.prune_file_list(self)
        to_remove = ["doc/build", "doc/pdf", "doc/html", "pylint", "epydoc"]
        for rm in to_remove:
            self.filelist.exclude_pattern(pattern="*", anchor=False, prefix=rm)

        # this is for Cython files specifically: remove C & html files
        search_root = os.path.dirname(os.path.abspath(__file__))
        for root, _, files in os.walk(search_root):
            for afile in files:
                if os.path.splitext(afile)[1].lower() == ".pyx":
payno's avatar
payno committed
285
                    base_file = os.path.join(root, afile)[len(search_root) + 1 : -4]
payno's avatar
payno committed
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
                    self.filelist.exclude_pattern(pattern=base_file + ".c")
                    self.filelist.exclude_pattern(pattern=base_file + ".cpp")
                    self.filelist.exclude_pattern(pattern=base_file + ".html")

    def make_distribution(self):
        self.prune_file_list()
        sdist.make_distribution(self)
        dest = self.archive_files[0]
        dirname, basename = os.path.split(dest)
        base, ext = os.path.splitext(basename)
        while ext in [".zip", ".tar", ".bz2", ".gz", ".Z", ".lz", ".orig"]:
            base, ext = os.path.splitext(base)
        # if ext:
        #     dest = "".join((base, ext))
        # else:
        #     dest = base
        # sp = dest.split("-")
        # base = sp[:-1]
        # nr = sp[-1]
        debian_arch = os.path.join(dirname, self.get_debian_name() + ".orig.tar.gz")
        os.rename(self.archive_files[0], debian_arch)
        self.archive_files = [debian_arch]
        print("Building debian .orig.tar.gz in %s" % self.archive_files[0])


# ##### #
# setup #
# ##### #

payno's avatar
payno committed
315

payno's avatar
payno committed
316
317
318
319
320
def get_project_configuration(dry_run):
    """Returns project arguments for setup"""
    install_requires = [
        # for the script launcher and pkg_resources
        "setuptools",
payno's avatar
payno committed
321
        "silx >= 0.14",
Pierre Paleo's avatar
Fix #9    
Pierre Paleo committed
322
        "lxml",
payno's avatar
payno committed
323
        "h5py >= 3.0",
Pierre Paleo's avatar
Pierre Paleo committed
324
    ]
payno's avatar
payno committed
325

payno's avatar
payno committed
326
327
328
    setup_requires = [
        "setuptools",
    ]
payno's avatar
payno committed
329
330

    # extras requirements: target 'full' to install all dependencies at once
payno's avatar
payno committed
331
    full_requires = []
payno's avatar
payno committed
332
333

    extras_require = {
payno's avatar
payno committed
334
        "full": full_requires,
payno's avatar
payno committed
335
336
    }

payno's avatar
payno committed
337
    package_data = {}
payno's avatar
payno committed
338

payno's avatar
payno committed
339
    entry_points = {}
payno's avatar
payno committed
340
341

    cmdclass = dict(
payno's avatar
payno committed
342
343
        build=Build, test=PyTest, build_doc=BuildDocCommand, debian_src=sdist_debian
    )
payno's avatar
payno committed
344
345
346
347
348
349
350
351
352
353
354
355

    if dry_run:
        # DRY_RUN implies actions which do not require NumPy
        #
        # And they are required to succeed without Numpy for example when
        # pip is used to install tomoscan when Numpy is not yet present in
        # the system.
        setup_kwargs = {}
    else:
        config = configuration()
        setup_kwargs = config.todict()

payno's avatar
payno committed
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
    setup_kwargs.update(
        name=PROJECT,
        version=get_version(),
        url="https://gitlab.esrf.fr/tomotools/tomoscan.git",
        author="data analysis unit",
        author_email="henri.payno@esrf.fr",
        classifiers=classifiers,
        description="utilitary to access tomography data at esrf",
        long_description=get_readme(),
        install_requires=install_requires,
        setup_requires=setup_requires,
        extras_require=extras_require,
        cmdclass=cmdclass,
        package_data=package_data,
        zip_safe=False,
        entry_points=entry_points,
    )
payno's avatar
payno committed
373
374
375
376
377
378
379
380
381
382
383
    return setup_kwargs


def setup_package():
    """Run setup(**kwargs)

    Depending on the command, it either runs the complete setup which depends on numpy,
    or a *dry run* setup with no dependency on numpy.
    """

    # Check if action requires build/install
payno's avatar
payno committed
384
385
386
387
388
389
390
391
    dry_run = len(sys.argv) == 1 or (
        len(sys.argv) >= 2
        and (
            "--help" in sys.argv[1:]
            or sys.argv[1]
            in ("--help-commands", "egg_info", "--version", "clean", "--name")
        )
    )
payno's avatar
payno committed
392
393
394
395
396

    if dry_run:
        # DRY_RUN implies actions which do not require dependencies, like NumPy
        try:
            from setuptools import setup
payno's avatar
payno committed
397

payno's avatar
payno committed
398
399
400
            logger.info("Use setuptools.setup")
        except ImportError:
            from distutils.core import setup
payno's avatar
payno committed
401

payno's avatar
payno committed
402
403
404
405
406
407
            logger.info("Use distutils.core.setup")
    else:
        try:
            from setuptools import setup
        except ImportError:
            from numpy.distutils.core import setup
payno's avatar
payno committed
408

payno's avatar
payno committed
409
410
411
412
413
414
415
416
            logger.info("Use numpy.distutils.setup")

    setup_kwargs = get_project_configuration(dry_run)
    setup(**setup_kwargs)


if __name__ == "__main__":
    setup_package()