blob: 353b6c096930fc0820009755b44b5eaabe49c50f [file] [log] [blame]
#!/usr/bin/env python3
""" NumPy is the fundamental package for array computing with Python.
It provides:
- 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
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.
All NumPy wheels distributed on PyPI are BSD licensed.
NumPy requires ``pytest`` and ``hypothesis``. Tests can then be run after
installation with::
python -c 'import numpy; numpy.test()'
"""
DOCLINES = (__doc__ or '').split("\n")
import os
import sys
import subprocess
import textwrap
import warnings
import builtins
import re
# Python supported version checks. Keep right after stdlib imports to ensure we
# get a sensible error for older Python versions
if sys.version_info[:2] < (3, 8):
raise RuntimeError("Python version >= 3.8 required.")
import versioneer
# 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.
builtins.__NUMPY_SETUP__ = True
# Needed for backwards code compatibility below and in some CI scripts.
# The version components are changed from ints to strings, but only VERSION
# seems to matter outside of this module and it was already a str.
FULLVERSION = versioneer.get_version()
# Capture the version string:
# 1.22.0.dev0+ ... -> ISRELEASED == False, VERSION == 1.22.0
# 1.22.0rc1+ ... -> ISRELEASED == False, VERSION == 1.22.0
# 1.22.0 ... -> ISRELEASED == True, VERSION == 1.22.0
# 1.22.0rc1 ... -> ISRELEASED == True, VERSION == 1.22.0
ISRELEASED = re.search(r'(dev|\+)', FULLVERSION) is None
_V_MATCH = re.match(r'(\d+)\.(\d+)\.(\d+)', FULLVERSION)
if _V_MATCH is None:
raise RuntimeError(f'Cannot parse version {FULLVERSION}')
MAJOR, MINOR, MICRO = _V_MATCH.groups()
VERSION = '{}.{}.{}'.format(MAJOR, MINOR, MICRO)
# The first version not in the `Programming Language :: Python :: ...` classifiers above
if sys.version_info >= (3, 11):
fmt = "NumPy {} may not yet support Python {}.{}."
warnings.warn(
fmt.format(VERSION, *sys.version_info[:2]),
RuntimeWarning)
del fmt
# 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')
# We need to import setuptools here in order for it to persist in sys.modules.
# Its presence/absence is used in subclassing setup in numpy/distutils/core.py.
# However, we need to run the distutils version of sdist, so import that first
# so that it is in sys.modules
import numpy.distutils.command.sdist
import setuptools
if int(setuptools.__version__.split('.')[0]) >= 60:
# setuptools >= 60 switches to vendored distutils by default; this
# may break the numpy build, so make sure the stdlib version is used
try:
setuptools_use_distutils = os.environ['SETUPTOOLS_USE_DISTUTILS']
except KeyError:
os.environ['SETUPTOOLS_USE_DISTUTILS'] = "stdlib"
else:
if setuptools_use_distutils != "stdlib":
raise RuntimeError("setuptools versions >= '60.0.0' require "
"SETUPTOOLS_USE_DISTUTILS=stdlib in the environment")
# Initialize cmdclass from versioneer
from numpy.distutils.core import numpy_cmdclass
cmdclass = versioneer.get_cmdclass(numpy_cmdclass)
CLASSIFIERS = """\
Development Status :: 5 - Production/Stable
Intended Audience :: Science/Research
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Programming Language :: C
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: Implementation :: CPython
Topic :: Software Development
Topic :: Scientific/Engineering
Typing :: Typed
Operating System :: Microsoft :: Windows
Operating System :: POSIX
Operating System :: Unix
Operating System :: MacOS
"""
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)
config.add_subpackage('numpy')
config.add_data_files(('numpy', 'LICENSE.txt'))
config.add_data_files(('numpy', 'numpy/*.pxd'))
config.get_version('numpy/version.py') # sets config.version
return config
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 line in f:
if 'path' in line:
p = line.split('=')[-1].strip()
if not os.path.exists(p):
raise ValueError('Submodule {} missing'.format(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: {}'.format(line))
class concat_license_files():
"""Merge LICENSE.txt and LICENSES_bundled.txt for sdist creation
Done this way to keep LICENSE.txt in repo as exact BSD 3-clause (see
gh-13447). This makes GitHub state correctly how NumPy is licensed.
"""
def __init__(self):
self.f1 = 'LICENSE.txt'
self.f2 = 'LICENSES_bundled.txt'
def __enter__(self):
"""Concatenate files and remove LICENSES_bundled.txt"""
with open(self.f1, 'r') as f1:
self.bsd_text = f1.read()
with open(self.f1, 'a') as f1:
with open(self.f2, 'r') as f2:
self.bundled_text = f2.read()
f1.write('\n\n')
f1.write(self.bundled_text)
def __exit__(self, exception_type, exception_value, traceback):
"""Restore content of both files"""
with open(self.f1, 'w') as f:
f.write(self.bsd_text)
# Need to inherit from versioneer version of sdist to get the encoded
# version information.
class sdist_checked(cmdclass['sdist']):
""" check submodules on sdist to prevent incomplete tarballs """
def run(self):
check_submodules()
with concat_license_files():
super().run()
def get_build_overrides():
"""
Custom build commands to add `-std=c99` to compilation
"""
from numpy.distutils.command.build_clib import build_clib
from numpy.distutils.command.build_ext import build_ext
from numpy.compat import _pep440
def _needs_gcc_c99_flag(obj):
if obj.compiler.compiler_type != 'unix':
return False
cc = obj.compiler.compiler[0]
if "gcc" not in cc:
return False
# will print something like '4.2.1\n'
out = subprocess.run([cc, '-dumpversion'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, universal_newlines=True)
# -std=c99 is default from this version on
if _pep440.parse(out.stdout) >= _pep440.Version('5.0'):
return False
return True
class new_build_clib(build_clib):
def build_a_library(self, build_info, lib_name, libraries):
from numpy.distutils.ccompiler_opt import NPY_CXX_FLAGS
if _needs_gcc_c99_flag(self):
build_info['extra_cflags'] = ['-std=c99']
build_info['extra_cxxflags'] = NPY_CXX_FLAGS
build_clib.build_a_library(self, build_info, lib_name, libraries)
class new_build_ext(build_ext):
def build_extension(self, ext):
if _needs_gcc_c99_flag(self):
if '-std=c99' not in ext.extra_compile_args:
ext.extra_compile_args.append('-std=c99')
build_ext.build_extension(self, ext)
return new_build_clib, new_build_ext
def generate_cython():
# Check Cython version
from numpy.compat import _pep440
try:
# try the cython in the installed python first (somewhat related to
# scipy/scipy#2397)
import Cython
from Cython.Compiler.Version import version as cython_version
except ImportError as e:
# The `cython` command need not point to the version installed in the
# Python running this script, so raise an error to avoid the chance of
# using the wrong version of Cython.
msg = 'Cython needs to be installed in Python as a module'
raise OSError(msg) from e
else:
# Note: keep in sync with that in pyproject.toml
# Update for Python 3.11
required_version = '0.29.30'
if _pep440.parse(cython_version) < _pep440.Version(required_version):
cython_path = Cython.__file__
msg = 'Building NumPy requires Cython >= {}, found {} at {}'
msg = msg.format(required_version, cython_version, cython_path)
raise RuntimeError(msg)
# Process files
cwd = os.path.abspath(os.path.dirname(__file__))
print("Cythonizing sources")
for d in ('random',):
p = subprocess.call([sys.executable,
os.path.join(cwd, 'tools', 'cythonize.py'),
'numpy/{0}'.format(d)],
cwd=cwd)
if p != 0:
raise RuntimeError("Running cythonize failed!")
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).
"""
args = sys.argv[1:]
if not args:
# 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',
'version',]
for command in info_commands:
if command in args:
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', 'build_src',
'bdist_egg')
for command in good_commands:
if command in args:
return True
# The following commands are supported, but we need to show more
# useful messages to the user
if 'install' in args:
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)
- `pip install numpy` (last NumPy release on PyPI)
"""))
return True
if '--help' in args or '-h' in sys.argv[1]:
print(textwrap.dedent("""
NumPy-specific help
-------------------
To install NumPy from here with reliable uninstall, we recommend
that you use `pip install .`. To install the latest NumPy release
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
# 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.
""",
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)
""",
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']
for command in ('upload_docs', 'easy_install', 'bdist', 'bdist_dumb',
'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():
if command in args:
print(textwrap.dedent(bad_commands[command]) +
"\nAdd `--force` to your command to use it anyway if you "
"must (unsupported).\n")
sys.exit(1)
# Commands that do more than print info, but also don't need Cython and
# template parsing.
other_commands = ['egg_info', 'install_egg_info', 'rotate', 'dist_info']
for command in other_commands:
if command in args:
return False
# If we got here, we didn't detect what setup.py command was given
raise RuntimeError("Unrecognized setuptools command: {}".format(args))
def get_docs_url():
if 'dev' in VERSION:
return "https://numpy.org/devdocs"
else:
# For releases, this URL ends up on PyPI.
# By pinning the version, users looking at old PyPI releases can get
# to the associated docs easily.
return "https://numpy.org/doc/{}.{}".format(MAJOR, MINOR)
def setup_package():
src_path = os.path.dirname(os.path.abspath(__file__))
old_path = os.getcwd()
os.chdir(src_path)
sys.path.insert(0, src_path)
# 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],
]
cmdclass["sdist"] = sdist_checked
metadata = dict(
name='numpy',
maintainer="NumPy Developers",
maintainer_email="numpy-discussion@python.org",
description=DOCLINES[0],
long_description="\n".join(DOCLINES[2:]),
url="https://www.numpy.org",
author="Travis E. Oliphant et al.",
download_url="https://pypi.python.org/pypi/numpy",
project_urls={
"Bug Tracker": "https://github.com/numpy/numpy/issues",
"Documentation": get_docs_url(),
"Source Code": "https://github.com/numpy/numpy",
},
license='BSD',
classifiers=[_f for _f in CLASSIFIERS.split('\n') if _f],
platforms=["Windows", "Linux", "Solaris", "Mac OS-X", "Unix"],
test_suite='pytest',
version=versioneer.get_version(),
cmdclass=cmdclass,
python_requires='>=3.8',
zip_safe=False,
entry_points={
'console_scripts': f2py_cmds,
'array_api': ['numpy = numpy.array_api'],
'pyinstaller40': ['hook-dirs = numpy:_pyinstaller_hooks_dir'],
},
)
if "--force" in sys.argv:
run_build = True
sys.argv.remove('--force')
else:
# Raise errors for unsupported commands, improve help output, etc.
run_build = parse_setuppy_commands()
if run_build:
# patches distutils, even though we don't use it
#from setuptools import setup
from numpy.distutils.core import setup
if 'sdist' not in sys.argv:
# Generate Cython sources, unless we're generating an sdist
generate_cython()
metadata['configuration'] = configuration
# Customize extension building
cmdclass['build_clib'], cmdclass['build_ext'] = get_build_overrides()
else:
#from numpy.distutils.core import setup
from setuptools import setup
try:
setup(**metadata)
finally:
del sys.path[0]
os.chdir(old_path)
return
if __name__ == '__main__':
setup_package()
# 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__