setup.py 14.4 KB
Newer Older
1
#!/usr/bin/env python
2
""" NumPy is the fundamental package for array computing with Python.
3

4
It provides:
5

6 7 8 9 10
- a powerful N-dimensional array object
- sophisticated (broadcasting) functions
- tools for integrating C/C++ and Fortran code
- useful linear algebra, Fourier transform, and random number capabilities
- and much more
11

12 13 14 15
Besides its obvious scientific uses, NumPy can also be used as an efficient
multi-dimensional container of generic data. Arbitrary data-types can be
defined. This allows NumPy to seamlessly and speedily integrate with a wide
variety of databases.
16

17
All NumPy wheels distributed on PyPI are BSD licensed.
18

19
"""
20
from __future__ import division, print_function
21

22
DOCLINES = (__doc__ or '').split("\n")
23 24 25

import os
import sys
26
import subprocess
27
import textwrap
28

29

30 31
if sys.version_info[:2] < (2, 7) or (3, 0) <= sys.version_info[:2] < (3, 4):
    raise RuntimeError("Python version 2.7 or >= 3.4 required.")
32 33

if sys.version_info[0] >= 3:
34
    import builtins
35 36
else:
    import __builtin__ as builtins
37

38

39
CLASSIFIERS = """\
40
Development Status :: 5 - Production/Stable
41 42 43 44 45
Intended Audience :: Science/Research
Intended Audience :: Developers
License :: OSI Approved
Programming Language :: C
Programming Language :: Python
46 47
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
48
Programming Language :: Python :: 3
49 50
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
51
Programming Language :: Python :: 3.6
52
Programming Language :: Python :: 3.7
53
Programming Language :: Python :: Implementation :: CPython
54 55 56 57 58 59 60 61
Topic :: Software Development
Topic :: Scientific/Engineering
Operating System :: Microsoft :: Windows
Operating System :: POSIX
Operating System :: Unix
Operating System :: MacOS
"""

62
MAJOR               = 1
63
MINOR               = 16
64
MICRO               = 2
65
ISRELEASED          = True
66
VERSION             = '%d.%d.%d' % (MAJOR, MINOR, MICRO)
67

68

69 70
# Return the git revision as a string
def git_version():
71 72 73
    def _minimal_ext_cmd(cmd):
        # construct minimal environment
        env = {}
74
        for k in ['SYSTEMROOT', 'PATH', 'HOME']:
75 76 77 78 79 80 81
            v = os.environ.get(k)
            if v is not None:
                env[k] = v
        # LANGUAGE is used on win32
        env['LANGUAGE'] = 'C'
        env['LANG'] = 'C'
        env['LC_ALL'] = 'C'
82
        out = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=env).communicate()[0]
83 84
        return out

85
    try:
86 87
        out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD'])
        GIT_REVISION = out.strip().decode('ascii')
88
    except OSError:
89
        GIT_REVISION = "Unknown"
90

91
    return GIT_REVISION
92

93 94 95 96 97
# BEFORE importing setuptools, remove MANIFEST. Otherwise it may not be
# properly updated when the contents of directories change (true for distutils,
# not sure about setuptools).
if os.path.exists('MANIFEST'):
    os.remove('MANIFEST')
98

99 100 101 102
# This is a bit hackish: we are setting a global variable so that the main
# numpy __init__ can detect if it is being loaded by the setup routine, to
# avoid attempting to load components that aren't built yet.  While ugly, it's
# a lot more robust than what was previously being used.
103
builtins.__NUMPY_SETUP__ = True
104

105

106
def get_version_info():
107 108 109 110 111 112 113
    # Adding the git rev number needs to be done inside write_version_py(),
    # otherwise the import of numpy.version messes up the build under Python 3.
    FULLVERSION = VERSION
    if os.path.exists('.git'):
        GIT_REVISION = git_version()
    elif os.path.exists('numpy/version.py'):
        # must be a source distribution, use existing version file
114 115 116 117 118 119
        try:
            from numpy.version import git_revision as GIT_REVISION
        except ImportError:
            raise ImportError("Unable to import git_revision. Try removing " \
                              "numpy/version.py and the build directory " \
                              "before building.")
120 121 122 123
    else:
        GIT_REVISION = "Unknown"

    if not ISRELEASED:
124
        FULLVERSION += '.dev0+' + GIT_REVISION[:7]
125

126 127 128 129 130 131
    return FULLVERSION, GIT_REVISION


def write_version_py(filename='numpy/version.py'):
    cnt = """
# THIS FILE IS GENERATED FROM NUMPY SETUP.PY
132 133
#
# To compare versions robustly, use `numpy.lib.NumpyVersion`
134 135 136 137 138 139 140 141 142 143 144
short_version = '%(version)s'
version = '%(version)s'
full_version = '%(full_version)s'
git_revision = '%(git_revision)s'
release = %(isrelease)s

if not release:
    version = full_version
"""
    FULLVERSION, GIT_REVISION = get_version_info()

145 146
    a = open(filename, 'w')
    try:
147
        a.write(cnt % {'version': VERSION,
148 149
                       'full_version': FULLVERSION,
                       'git_revision': GIT_REVISION,
150
                       'isrelease': str(ISRELEASED)})
151 152 153
    finally:
        a.close()

154

155 156 157 158 159 160 161 162
def configuration(parent_package='',top_path=None):
    from numpy.distutils.misc_util import Configuration

    config = Configuration(None, parent_package, top_path)
    config.set_options(ignore_setup_xxx_py=True,
                       assume_default_configuration=True,
                       delegate_options_to_subpackages=True,
                       quiet=True)
163

164
    config.add_subpackage('numpy')
165
    config.add_data_files(('numpy', 'LICENSE.txt'))
166

167
    config.get_version('numpy/version.py') # sets config.version
168

169 170
    return config

171

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
def check_submodules():
    """ verify that the submodules are checked out and clean
        use `git submodule update --init`; on failure
    """
    if not os.path.exists('.git'):
        return
    with open('.gitmodules') as f:
        for l in f:
            if 'path' in l:
                p = l.split('=')[-1].strip()
                if not os.path.exists(p):
                    raise ValueError('Submodule %s missing' % p)


    proc = subprocess.Popen(['git', 'submodule', 'status'],
                            stdout=subprocess.PIPE)
    status, _ = proc.communicate()
    status = status.decode("ascii", "replace")
    for line in status.splitlines():
        if line.startswith('-') or line.startswith('+'):
            raise ValueError('Submodule not clean: %s' % line)

194

195 196 197 198 199 200
from distutils.command.sdist import sdist
class sdist_checked(sdist):
    """ check submodules on sdist to prevent incomplete tarballs """
    def run(self):
        check_submodules()
        sdist.run(self)
201

202

203 204 205 206 207 208 209 210 211 212
def generate_cython():
    cwd = os.path.abspath(os.path.dirname(__file__))
    print("Cythonizing sources")
    p = subprocess.call([sys.executable,
                          os.path.join(cwd, 'tools', 'cythonize.py'),
                          'numpy/random'],
                         cwd=cwd)
    if p != 0:
        raise RuntimeError("Running cythonize failed!")

213 214 215 216 217 218 219

def parse_setuppy_commands():
    """Check the commands and respond appropriately.  Disable broken commands.

    Return a boolean value for whether or not to run the build or not (avoid
    parsing Cython and template files if False).
    """
220 221 222
    args = sys.argv[1:]

    if not args:
223 224 225 226 227 228 229 230 231 232 233
        # User forgot to give an argument probably, let setuptools handle that.
        return True

    info_commands = ['--help-commands', '--name', '--version', '-V',
                     '--fullname', '--author', '--author-email',
                     '--maintainer', '--maintainer-email', '--contact',
                     '--contact-email', '--url', '--license', '--description',
                     '--long-description', '--platforms', '--classifiers',
                     '--keywords', '--provides', '--requires', '--obsoletes']

    for command in info_commands:
234
        if command in args:
235 236 237 238 239 240 241 242 243 244
            return False

    # Note that 'alias', 'saveopts' and 'setopt' commands also seem to work
    # fine as they are, but are usually used together with one of the commands
    # below and not standalone.  Hence they're not added to good_commands.
    good_commands = ('develop', 'sdist', 'build', 'build_ext', 'build_py',
                     'build_clib', 'build_scripts', 'bdist_wheel', 'bdist_rpm',
                     'bdist_wininst', 'bdist_msi', 'bdist_mpkg')

    for command in good_commands:
245
        if command in args:
246 247 248 249
            return True

    # The following commands are supported, but we need to show more
    # useful messages to the user
250
    if 'install' in args:
251 252 253 254 255 256
        print(textwrap.dedent("""
            Note: if you need reliable uninstall behavior, then install
            with pip instead of using `setup.py install`:

              - `pip install .`       (from a git repo or downloaded source
                                       release)
257
              - `pip install numpy`   (last NumPy release on PyPi)
258 259 260 261

            """))
        return True

262
    if '--help' in args or '-h' in sys.argv[1]:
263
        print(textwrap.dedent("""
264
            NumPy-specific help
265 266
            -------------------

267 268
            To install NumPy from here with reliable uninstall, we recommend
            that you use `pip install .`. To install the latest NumPy release
269 270 271 272 273 274 275 276 277 278 279
            from PyPi, use `pip install numpy`.

            For help with build/installation issues, please ask on the
            numpy-discussion mailing list.  If you are sure that you have run
            into a bug, please report it at https://github.com/numpy/numpy/issues.

            Setuptools commands help
            ------------------------
            """))
        return False

280

281 282 283 284 285 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 315 316
    # The following commands aren't supported.  They can only be executed when
    # the user explicitly adds a --force command-line argument.
    bad_commands = dict(
        test="""
            `setup.py test` is not supported.  Use one of the following
            instead:

              - `python runtests.py`              (to build and test)
              - `python runtests.py --no-build`   (to test installed numpy)
              - `>>> numpy.test()`           (run tests for installed numpy
                                              from within an interpreter)
            """,
        upload="""
            `setup.py upload` is not supported, because it's insecure.
            Instead, build what you want to upload and upload those files
            with `twine upload -s <filenames>` instead.
            """,
        upload_docs="`setup.py upload_docs` is not supported",
        easy_install="`setup.py easy_install` is not supported",
        clean="""
            `setup.py clean` is not supported, use one of the following instead:

              - `git clean -xdf` (cleans all files)
              - `git clean -Xdf` (cleans all versioned files, doesn't touch
                                  files that aren't checked into the git repo)
            """,
        check="`setup.py check` is not supported",
        register="`setup.py register` is not supported",
        bdist_dumb="`setup.py bdist_dumb` is not supported",
        bdist="`setup.py bdist` is not supported",
        build_sphinx="""
            `setup.py build_sphinx` is not supported, use the
            Makefile under doc/""",
        flake8="`setup.py flake8` is not supported, use flake8 standalone",
        )
    bad_commands['nosetests'] = bad_commands['test']
317
    for command in ('upload_docs', 'easy_install', 'bdist', 'bdist_dumb',
318 319 320 321 322
                     'register', 'check', 'install_data', 'install_headers',
                     'install_lib', 'install_scripts', ):
        bad_commands[command] = "`setup.py %s` is not supported" % command

    for command in bad_commands.keys():
323
        if command in args:
324 325 326 327 328
            print(textwrap.dedent(bad_commands[command]) +
                  "\nAdd `--force` to your command to use it anyway if you "
                  "must (unsupported).\n")
            sys.exit(1)

329 330 331 332 333 334 335
    # Commands that do more than print info, but also don't need Cython and
    # template parsing.
    other_commands = ['egg_info', 'install_egg_info', 'rotate']
    for command in other_commands:
        if command in args:
            return False

336 337 338
    # If we got here, we didn't detect what setup.py command was given
    import warnings
    warnings.warn("Unrecognized setuptools command, proceeding with "
339
                  "generating Cython sources and expanding templates", stacklevel=2)
340 341 342
    return True


343
def setup_package():
344
    src_path = os.path.dirname(os.path.abspath(sys.argv[0]))
345 346 347 348
    old_path = os.getcwd()
    os.chdir(src_path)
    sys.path.insert(0, src_path)

349 350 351
    # Rewrite the version file everytime
    write_version_py()

352 353 354 355 356 357 358 359 360 361 362 363
    # The f2py scripts that will be installed
    if sys.platform == 'win32':
        f2py_cmds = [
            'f2py = numpy.f2py.f2py2e:main',
            ]
    else:
        f2py_cmds = [
            'f2py = numpy.f2py.f2py2e:main',
            'f2py%s = numpy.f2py.f2py2e:main' % sys.version_info[:1],
            'f2py%s.%s = numpy.f2py.f2py2e:main' % sys.version_info[:2],
            ]

364 365 366
    metadata = dict(
        name = 'numpy',
        maintainer = "NumPy Developers",
367
        maintainer_email = "numpy-discussion@python.org",
368 369
        description = DOCLINES[0],
        long_description = "\n".join(DOCLINES[2:]),
370
        url = "https://www.numpy.org",
371
        author = "Travis E. Oliphant et al.",
372
        download_url = "https://pypi.python.org/pypi/numpy",
373 374 375 376 377
        license = 'BSD',
        classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f],
        platforms = ["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"],
        test_suite='nose.collector',
        cmdclass={"sdist": sdist_checked},
378
        python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
379
        zip_safe=False,
380 381 382
        entry_points={
            'console_scripts': f2py_cmds
        },
383 384
    )

385 386
    if "--force" in sys.argv:
        run_build = True
387
        sys.argv.remove('--force')
388
    else:
389 390 391 392 393
        # Raise errors for unsupported commands, improve help output, etc.
        run_build = parse_setuppy_commands()

    from setuptools import setup
    if run_build:
394
        from numpy.distutils.core import setup
395 396 397 398
        cwd = os.path.abspath(os.path.dirname(__file__))
        if not os.path.exists(os.path.join(cwd, 'PKG-INFO')):
            # Generate Cython sources, unless building from source release
            generate_cython()
399

400
        metadata['configuration'] = configuration
401 402 403 404
    else:
        # Version number is added to metadata inside configuration() if build
        # is run.
        metadata['version'] = get_version_info()[0]
405

406
    try:
407
        setup(**metadata)
408 409 410 411 412
    finally:
        del sys.path[0]
        os.chdir(old_path)
    return

413

414 415
if __name__ == '__main__':
    setup_package()
416 417 418 419 420
    # This may avoid problems where numpy is installed via ``*_requires`` by
    # setuptools, the global namespace isn't reset properly, and then numpy is
    # imported later (which will then fail to load numpy extension modules).
    # See gh-7956 for details
    del builtins.__NUMPY_SETUP__