diff options
| author | Ralf Gommers <ralf.gommers@gmail.com> | 2022-11-23 23:40:23 +0100 |
|---|---|---|
| committer | Ralf Gommers <ralf.gommers@gmail.com> | 2022-11-25 12:37:46 +0100 |
| commit | 4002a7d421ff10780c28a3643683af7a9754f87f (patch) | |
| tree | a06d4c10642d1c93d552f80f1e90f393c9953c18 | |
| parent | 632f573f12c641990bfac24cf4df435804340a7f (diff) | |
| download | numpy-4002a7d421ff10780c28a3643683af7a9754f87f.tar.gz | |
BLD: enable building NumPy with Meson
This enables building with NumPy on Linux and macOS. Windows support
should be complete to, but is untested as of now and may need a few
tweaks. This contains:
- A set of `meson.build` files and related code generation script
tweaks, header templates, etc.
- One CI job on Linux
- Basic docs on using Meson to build NumPy (not yet integrated in the
html docs, it's too early for that - this is for early adopters right
now).
The build should be complete, with the major exception of SIMD support.
The full test suite passes. See gh-22546 for the tracking issue with
detailed notes on the plan for switching NumPy to Meson as its build
system.
Co-authored-by: Stefan van der Walt <stefanv@berkeley.edu>
39 files changed, 2549 insertions, 50 deletions
diff --git a/.github/workflows/linux_meson.yml b/.github/workflows/linux_meson.yml new file mode 100644 index 000000000..9b21820fb --- /dev/null +++ b/.github/workflows/linux_meson.yml @@ -0,0 +1,60 @@ +name: Test Meson build (Linux) + +on: + push: + branches: + - main + - maintenance/** + pull_request: + branches: + - main + - maintenance/** + +defaults: + run: + shell: bash + +env: + PYTHON_VERSION: 3.11 + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + meson_devpy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Install dependencies + run: | + pip install -r build_requirements.txt + sudo apt-get install -y libopenblas-serial-dev + - name: Build + shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"' + env: + TERM: xterm-256color + run: + ./dev.py build -- --werror + - name: Check build-internal dependencies + run: + ninja -C build -t missingdeps + - name: Check installed test and stub files + run: + python tools/check_installed_files.py $(find ./build-install -path '*/site-packages/numpy') + - name: Test + shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"' + env: + TERM: xterm-256color + run: | + pip install pytest hypothesis typing_extensions + ./dev.py test diff --git a/.gitignore b/.gitignore index e2ee6a013..f2a0980a4 100644 --- a/.gitignore +++ b/.gitignore @@ -61,8 +61,11 @@ GTAGS # Python files # ################ -# setup.py working directory +# meson build/installation directories build +build-install +# meson python output +.mesonpy-native-file.ini # sphinx build directory _build # setup.py dist directory diff --git a/build_requirements.txt b/build_requirements.txt new file mode 100644 index 000000000..fccc2e4fa --- /dev/null +++ b/build_requirements.txt @@ -0,0 +1,5 @@ +meson-python>=0.10.0 +Cython>=0.29.30,<3.0 +wheel==0.37.0 +ninja +git+https://github.com/scientific-python/devpy diff --git a/building_with_meson.md b/building_with_meson.md new file mode 100644 index 000000000..dd1ca1d94 --- /dev/null +++ b/building_with_meson.md @@ -0,0 +1,65 @@ +# Building with Meson + +_Note: this is for early adopters. It has been tested on Linux and macOS, and +with Python 3.9-3.12. Windows will be tested soon. There is one CI job to keep +the build stable. This may have rough edges, please open an issue if you run +into a problem._ + +### Developer build + +**Install build tools:** Use one of: + +- `mamba create -f environment.yml +- `pip install -r build_requirements.txt # also make sure you have pkg-config and the usual system dependencies for NumPy` + +**Compile and install:** `./dev.py build` + +This builds in the `build/` directory, and installs into the `build-install` directory. + +Then run the test suite or a shell via `dev.py`: +``` +./dev.py test +./dev.py ipython +``` + +Alternatively, to use the package, add it to your `PYTHONPATH`: +``` +export PYTHONPATH=${PWD}/build/lib64/python3.10/site-packages # may vary +pytest --pyargs numpy +``` + + +### pip install + +Note that `pip` will use the default build system, which is (as of now) still +`numpy.distutils`. In order to switch that default to Meson, uncomment the +`build-backend = "mesonpy"` line at the top of `pyproject.toml`. + +After that is done, `pip install .` or `pip install --no-build-isolation .` +will work as expected. As does building an sdist or wheel with `python -m build`. +Note, however, that `pip install -e .` (in-place developer install) does not! +Use `dev.py` instead (see above). + + + +### Workaround for a hiccup on Fedora + +- Fedora does not distribute `openblas.pc`. Install the following file in `~/lib/pkgconfig/openblas.pc`: + +``` +prefix=/usr +includedir=${prefix}/include +libdir=${prefix}/lib64 + +Name: openblas +Description: OpenBLAS is an optimized BLAS library based on GotoBLAS2 1.13 BSD version +Version: 0.3.19 +Cflags: -I${includedir}/openblas +Libs: -L${libdir} -lopenblas +``` + +Then build with: + +``` +./dev.py build -- -Dpkg_config_path=${HOME}/lib/pkgconfig +``` @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# +# Example stub for running `python -m dev.py` +# +# Copy this into your project root. + +import os +import sys +import runpy + +sys.path.remove(os.path.abspath(os.path.dirname(sys.argv[0]))) +try: + runpy.run_module("devpy", run_name="__main__") +except ImportError: + print("Cannot import devpy; please install it using") + print() + print(" pip install git+https://github.com/scientific-python/devpy") + print() + sys.exit(1) diff --git a/doc/source/f2py/buildtools/meson.rst b/doc/source/f2py/buildtools/meson.rst index 7edc6722f..6b4392880 100644 --- a/doc/source/f2py/buildtools/meson.rst +++ b/doc/source/f2py/buildtools/meson.rst @@ -28,8 +28,7 @@ build system like ``meson``. We will acquire this by: Now, consider the following ``meson.build`` file for the ``fib`` and ``scalar`` examples from :ref:`f2py-getting-started` section: -.. literalinclude:: ./../code/meson.build - :language: meson +.. literalinclude:: ../code/meson.build At this point the build will complete, but the import will fail: @@ -93,8 +92,7 @@ for reasons discussed in :ref:`f2py-bldsys`. However, we can augment our workflow in a straightforward to take into account files for which the outputs are known when the build system is set up. -.. literalinclude:: ./../code/meson_upd.build - :language: meson +.. literalinclude:: ../code/meson_upd.build This can be compiled and run as before. diff --git a/doc/source/f2py/code/meson.build b/doc/source/f2py/code/meson.build index 04276a176..1d409f2fb 100644 --- a/doc/source/f2py/code/meson.build +++ b/doc/source/f2py/code/meson.build @@ -1,31 +1,35 @@ project('f2py_examples', 'c', version : '0.1', - default_options : ['warning_level=2']) + license: 'BSD-3', + meson_version: '>=0.64.0', + default_options : ['warning_level=2'], +) add_languages('fortran') py_mod = import('python') -py3 = py_mod.find_installation('python3') -py3_dep = py3.dependency() -message(py3.path()) -message(py3.get_install_dir()) +py = py_mod.find_installation(pure: false) +py_dep = py.dependency() -incdir_numpy = run_command(py3, +incdir_numpy = run_command(py, ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'], check : true ).stdout().strip() -incdir_f2py = run_command(py3, +incdir_f2py = run_command(py, ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'], check : true ).stdout().strip() inc_np = include_directories(incdir_numpy, incdir_f2py) -py3.extension_module('fib2', - 'fib1.f', - 'fib2module.c', - incdir_f2py+'/fortranobject.c', - include_directories: inc_np, - dependencies : py3_dep, - install : true)
\ No newline at end of file +py.extension_module('fib2', + [ + 'fib1.f', + 'fib2module.c', # note: this assumes f2py was manually run before! + ], + incdir_f2py / 'fortranobject.c', + include_directories: inc_np, + dependencies : py_dep, + install : true +) diff --git a/doc/source/f2py/code/meson_upd.build b/doc/source/f2py/code/meson_upd.build index 44d69d182..5e4b13bae 100644 --- a/doc/source/f2py/code/meson_upd.build +++ b/doc/source/f2py/code/meson_upd.build @@ -1,37 +1,38 @@ project('f2py_examples', 'c', version : '0.1', - default_options : ['warning_level=2']) + license: 'BSD-3', + meson_version: '>=0.64.0', + default_options : ['warning_level=2'], +) add_languages('fortran') py_mod = import('python') -py3 = py_mod.find_installation('python3') -py3_dep = py3.dependency() -message(py3.path()) -message(py3.get_install_dir()) +py = py_mod.find_installation(pure: false) +py_dep = py.dependency() -incdir_numpy = run_command(py3, +incdir_numpy = run_command(py, ['-c', 'import os; os.chdir(".."); import numpy; print(numpy.get_include())'], check : true ).stdout().strip() -incdir_f2py = run_command(py3, +incdir_f2py = run_command(py, ['-c', 'import os; os.chdir(".."); import numpy.f2py; print(numpy.f2py.get_include())'], check : true ).stdout().strip() fibby_source = custom_target('fibbymodule.c', - input : ['fib1.f'], # .f so no F90 wrappers - output : ['fibbymodule.c', 'fibby-f2pywrappers.f'], - command : [ py3, '-m', 'numpy.f2py', '@INPUT@', - '-m', 'fibby', '--lower']) + input : ['fib1.f'], # .f so no F90 wrappers + output : ['fibbymodule.c', 'fibby-f2pywrappers.f'], + command : [py, '-m', 'numpy.f2py', '@INPUT@', '-m', 'fibby', '--lower'] +) inc_np = include_directories(incdir_numpy, incdir_f2py) -py3.extension_module('fibby', - 'fib1.f', - fibby_source, - incdir_f2py+'/fortranobject.c', - include_directories: inc_np, - dependencies : py3_dep, - install : true) +py.extension_module('fibby', + ['fib1.f', fibby_source], + incdir_f2py / 'fortranobject.c', + include_directories: inc_np, + dependencies : py_dep, + install : true +) diff --git a/environment.yml b/environment.yml index b99fa1256..8dbb1f80d 100644 --- a/environment.yml +++ b/environment.yml @@ -13,6 +13,10 @@ dependencies: - openblas - nomkl - setuptools=59.2.0 + - meson >= 0.64.0 + - ninja + - pkg-config # note: not available on Windows, comment out this line + - meson-python # For testing - pytest - pytest-cov diff --git a/generate_version.py b/generate_version.py new file mode 100644 index 000000000..b3c8a3978 --- /dev/null +++ b/generate_version.py @@ -0,0 +1,40 @@ +# Note: This file has to live next to versioneer.py or it will not work +import argparse +import os + +import versioneer + + +def write_version_info(path): + vinfo = versioneer.get_versions() + full_version = vinfo['version'] + git_revision = vinfo['full-revisionid'] + + if os.environ.get("MESON_DIST_ROOT"): + path = os.path.join(os.environ.get("MESON_DIST_ROOT"), path) + + with open(path, "w") as f: + f.write("def get_versions():\n") + f.write(" return {\n") + f.write(f" 'full-revisionid': '{git_revision}',\n") + f.write(f" 'version': '{full_version}'\n") + f.write("}") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-o", "--outfile", type=str, help="Path to write version info to" + ) + args = parser.parse_args() + + if not args.outfile.endswith(".py"): + raise ValueError( + f"Output file must be a Python file. " + f"Got: {args.outfile} as filename instead" + ) + + write_version_info(args.outfile) + + +main() diff --git a/meson.build b/meson.build new file mode 100644 index 000000000..2a1a40384 --- /dev/null +++ b/meson.build @@ -0,0 +1,64 @@ +project( + 'NumPy', + 'c', 'cpp', 'cython', + # Note that the git commit hash cannot be added dynamically here + # It is dynamically added upon import by versioneer + # See `numpy/__init__.py` + version: '1.24.0.dev0', + license: 'BSD-3', + meson_version: '>= 0.64.0', + default_options: [ + 'buildtype=debugoptimized', + 'c_std=c99', + 'cpp_std=c++14', + 'blas=openblas', + 'lapack=openblas', + 'pkgconfig.relocatable=true', + ], +) + +fs = import('fs') + +cc = meson.get_compiler('c') +cpp = meson.get_compiler('cpp') + +# Check compiler is recent enough (see the SciPy Toolchain Roadmap for details) +if cc.get_id() == 'gcc' + if not cc.version().version_compare('>=8.4') + error('NumPy requires GCC >= 8.4') + endif +elif cc.get_id() == 'msvc' + if not cc.version().version_compare('>=19.20') + error('NumPy requires at least vc142 (default with Visual Studio 2019) ' + \ + 'when building with MSVC') + endif +endif + +# https://mesonbuild.com/Python-module.html +py_mod = import('python') +py = py_mod.find_installation(pure: false) +py_dep = py.dependency() + +if not cc.has_header('Python.h', dependencies: py_dep) + error('Cannot compile `Python.h`. Perhaps you need to install python-dev|python-devel') +endif + +# Generate version number. Note that this will not (yet) update the version +# number seen by pip or reflected in wheel filenames. See +# https://github.com/mesonbuild/meson-python/issues/159 for that. +versioneer = files('generate_version.py') +if fs.exists('_version_meson.py') + py.install_sources('_version_meson.py', subdir: 'numpy') +else + custom_target('write_version_file', + output: '_version_meson.py', + command: [py, versioneer, '-o', '@OUTPUT@'], + build_by_default: true, + build_always_stale: true, + install: true, + install_dir: py.get_install_dir() / 'numpy' + ) + meson.add_dist_script(py, versioneer, '-o', '_version_meson.py') +endif + +subdir('numpy') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 000000000..f53432def --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,13 @@ +option('blas', type: 'string', value: 'openblas', + description: 'option for BLAS library switching') +option('lapack', type: 'string', value: 'openblas', + description: 'option for LAPACK library switching') +option('disable-svml', type: 'boolean', value: 'false', + description: 'Disable building against SVML') +option('disable-threading', type: 'boolean', value: 'false', + description: 'Disable threading support (see `NPY_ALLOW_THREADS` docs)') +# TODO: flip value to 'false' once we have `npy_cpu_dispatch_config.h` & co. +option('disable-simd-optimizations', type: 'boolean', value: 'true', + description: 'Disable SIMD features beyond the baseline ones') +option('relaxed-strides-debug', type: 'boolean', value: 'false', + description: 'Enable relaxed strides debug mode (see `NPY_RELAXED_STRIDES_DEBUG` docs)') diff --git a/numpy/__config__.py.in b/numpy/__config__.py.in new file mode 100644 index 000000000..cda615904 --- /dev/null +++ b/numpy/__config__.py.in @@ -0,0 +1,134 @@ +# This file is generated by numpy's build process +# It contains system_info results at the time of building this package. +__all__ = ["get_info", "show"] + +import os +import sys + +extra_dll_dir = os.path.join(os.path.dirname(__file__), '.libs') + +if sys.platform == 'win32' and os.path.isdir(extra_dll_dir): + os.add_dll_directory(extra_dll_dir) + +blas_armpl_info={} +blas_mkl_info={} +blis_info={} +openblas_info={} +accelerate_info={} +atlas_3_10_blas_threads_info={} +atlas_3_10_blas_info={} +atlas_blas_threads_info={} +atlas_blas_info={} +blas_info={} +blas_src_info={} +blas_opt_info={} +lapack_armpl_info={} +lapack_mkl_info={} +openblas_lapack_info={} +openblas_clapack_info={} +flame_info={} +atlas_3_10_threads_info={} +atlas_3_10_info={} +atlas_threads_info={} +atlas_info={} +lapack_info={} +lapack_src_info={} +lapack_opt_info={} +numpy_linalg_lapack_lite={} + +def get_info(name): + g = globals() + return g.get(name, g.get(name + "_info", {})) + +def show(): + """ + Show libraries in the system on which NumPy was built. + + Print information about various resources (libraries, library + directories, include directories, etc.) in the system on which + NumPy was built. + + See Also + -------- + get_include : Returns the directory containing NumPy C + header files. + + Notes + ----- + 1. Classes specifying the information to be printed are defined + in the `numpy.distutils.system_info` module. + + Information may include: + + * ``language``: language used to write the libraries (mostly + C or f77) + * ``libraries``: names of libraries found in the system + * ``library_dirs``: directories containing the libraries + * ``include_dirs``: directories containing library header files + * ``src_dirs``: directories containing library source files + * ``define_macros``: preprocessor macros used by + ``distutils.setup`` + * ``baseline``: minimum CPU features required + * ``found``: dispatched features supported in the system + * ``not found``: dispatched features that are not supported + in the system + + 2. NumPy BLAS/LAPACK Installation Notes + + Installing a numpy wheel (``pip install numpy`` or force it + via ``pip install numpy --only-binary :numpy: numpy``) includes + an OpenBLAS implementation of the BLAS and LAPACK linear algebra + APIs. In this case, ``library_dirs`` reports the original build + time configuration as compiled with gcc/gfortran; at run time + the OpenBLAS library is in + ``site-packages/numpy.libs/`` (linux), or + ``site-packages/numpy/.dylibs/`` (macOS), or + ``site-packages/numpy/.libs/`` (windows). + + Installing numpy from source + (``pip install numpy --no-binary numpy``) searches for BLAS and + LAPACK dynamic link libraries at build time as influenced by + environment variables NPY_BLAS_LIBS, NPY_CBLAS_LIBS, and + NPY_LAPACK_LIBS; or NPY_BLAS_ORDER and NPY_LAPACK_ORDER; + or the optional file ``~/.numpy-site.cfg``. + NumPy remembers those locations and expects to load the same + libraries at run-time. + In NumPy 1.21+ on macOS, 'accelerate' (Apple's Accelerate BLAS + library) is in the default build-time search order after + 'openblas'. + + Examples + -------- + >>> import numpy as np + >>> np.show_config() + blas_opt_info: + language = c + define_macros = [('HAVE_CBLAS', None)] + libraries = ['openblas', 'openblas'] + library_dirs = ['/usr/local/lib'] + """ + from numpy.core._multiarray_umath import ( + __cpu_features__, __cpu_baseline__, __cpu_dispatch__ + ) + for name,info_dict in globals().items(): + if name[0] == "_" or type(info_dict) is not type({}): continue + print(name + ":") + if not info_dict: + print(" NOT AVAILABLE") + for k,v in info_dict.items(): + v = str(v) + if k == "sources" and len(v) > 200: + v = v[:60] + " ...\n... " + v[-60:] + print(" %s = %s" % (k,v)) + + features_found, features_not_found = [], [] + for feature in __cpu_dispatch__: + if __cpu_features__[feature]: + features_found.append(feature) + else: + features_not_found.append(feature) + + print("Supported SIMD extensions in this NumPy install:") + print(" baseline = %s" % (','.join(__cpu_baseline__))) + print(" found = %s" % (','.join(features_found))) + print(" not found = %s" % (','.join(features_not_found))) diff --git a/numpy/_build_utils/__init__.py b/numpy/_build_utils/__init__.py new file mode 100644 index 000000000..ac4908957 --- /dev/null +++ b/numpy/_build_utils/__init__.py @@ -0,0 +1,22 @@ +# Don't use the deprecated NumPy C API. Define this to a fixed version +# instead of NPY_API_VERSION in order not to break compilation for +# released SciPy versions when NumPy introduces a new deprecation. Use +# in setup.py:: +# +# config.add_extension('_name', sources=['source_fname'], **numpy_nodepr_api) +# +numpy_nodepr_api = dict( + define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_9_API_VERSION")] +) + + +def import_file(folder, module_name): + """Import a file directly, avoiding importing scipy""" + import importlib + import pathlib + + fname = pathlib.Path(folder) / f'{module_name}.py' + spec = importlib.util.spec_from_file_location(module_name, str(fname)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module diff --git a/numpy/_build_utils/gcc_build_bitness.py b/numpy/_build_utils/gcc_build_bitness.py new file mode 100644 index 000000000..fcad237e9 --- /dev/null +++ b/numpy/_build_utils/gcc_build_bitness.py @@ -0,0 +1,21 @@ +#!python +""" Detect bitness (32 or 64) of Mingw-w64 gcc build target on Windows. +""" + +import re +from subprocess import run, PIPE + + +def main(): + res = run(['gcc', '-v'], check=True, text=True, capture_output=True) + target = re.search(r'^Target: (.*)$', res.stderr, flags=re.M).groups()[0] + if target.startswith('i686'): + print('32') + elif target.startswith('x86_64'): + print('64') + else: + raise RuntimeError('Could not detect Mingw-w64 bitness') + + +if __name__ == "__main__": + main() diff --git a/numpy/_build_utils/process_src_template.py b/numpy/_build_utils/process_src_template.py new file mode 100644 index 000000000..4a0915e25 --- /dev/null +++ b/numpy/_build_utils/process_src_template.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +import sys +import os +import argparse +import importlib.util + + +def get_processor(): + # Convoluted because we can't import from numpy.distutils + # (numpy is not yet built) + conv_template_path = os.path.join( + os.path.dirname(__file__), + '..', 'distutils', 'conv_template.py' + ) + spec = importlib.util.spec_from_file_location( + 'conv_template', conv_template_path + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.process_file + + +def process_and_write_file(fromfile, outfile): + """Process tempita templated file and write out the result. + + The template file is expected to end in `.src` + (e.g., `.c.src` or `.h.src`). + Processing `npy_somefile.c.src` generates `npy_somefile.c`. + + """ + process_file = get_processor() + content = process_file(fromfile) + with open(outfile, 'w') as f: + f.write(content) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "infile", + type=str, + help="Path to the input file" + ) + parser.add_argument( + "-o", + "--outfile", + type=str, + help="Path to the output file" + ) + parser.add_argument( + "-i", + "--ignore", + type=str, + help="An ignored input - may be useful to add a " + "dependency between custom targets", + ) + args = parser.parse_args() + + if not args.infile.endswith('.src'): + raise ValueError(f"Unexpected extension: {args.infile}") + + outfile_abs = os.path.join(os.getcwd(), args.outfile) + process_and_write_file(args.infile, outfile_abs) + + +if __name__ == "__main__": + main() diff --git a/numpy/_build_utils/setup.py b/numpy/_build_utils/setup.py new file mode 100644 index 000000000..73a8f2fff --- /dev/null +++ b/numpy/_build_utils/setup.py @@ -0,0 +1,12 @@ +def configuration(parent_package='', top_path=None): + from numpy.distutils.misc_util import Configuration + + config = Configuration('_build_utils', parent_package, top_path) + config.add_data_dir('tests') + return config + + +if __name__ == '__main__': + from numpy.distutils.core import setup + + setup(**configuration(top_path='').todict()) diff --git a/numpy/_build_utils/tempita.py b/numpy/_build_utils/tempita.py new file mode 100755 index 000000000..3bcc789c5 --- /dev/null +++ b/numpy/_build_utils/tempita.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import sys +import os +import argparse + +from Cython import Tempita as tempita + +# XXX: If this import ever fails (does it really?), vendor either +# cython.tempita or numpy/npy_tempita. + + +def process_tempita(fromfile, outfile=None): + """Process tempita templated file and write out the result. + + The template file is expected to end in `.c.in` or `.pyx.in`: + E.g. processing `template.c.in` generates `template.c`. + + """ + if outfile is None: + # We're dealing with a distutils build here, write in-place + outfile = os.path.splitext(fromfile)[0] + + from_filename = tempita.Template.from_filename + template = from_filename(fromfile, encoding=sys.getdefaultencoding()) + + content = template.substitute() + + with open(outfile, 'w') as f: + f.write(content) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "infile", + type=str, + help="Path to the input file" + ) + parser.add_argument( + "-o", + "--outfile", + type=str, + help="Path to the output file" + ) + parser.add_argument( + "-i", + "--ignore", + type=str, + help="An ignored input - may be useful to add a " + "dependency between custom targets", + ) + args = parser.parse_args() + + if not args.infile.endswith('.in'): + raise ValueError(f"Unexpected extension: {args.infile}") + + outfile_abs = os.path.join(os.getcwd(), args.outfile) + process_tempita(args.infile, outfile_abs) + + +if __name__ == "__main__": + main() diff --git a/numpy/core/check_longdouble.c b/numpy/core/check_longdouble.c new file mode 100644 index 000000000..756ef3fc6 --- /dev/null +++ b/numpy/core/check_longdouble.c @@ -0,0 +1,15 @@ +/* "before" is 16 bytes to ensure there's no padding between it and "x". + * We're not expecting any "long double" bigger than 16 bytes or with + * alignment requirements stricter than 16 bytes. */ +typedef long double test_type; + +struct { + char before[16]; + test_type x; + char after[8]; +} foo = { + { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\001', '\043', '\105', '\147', '\211', '\253', '\315', '\357' }, + -123456789.0, + { '\376', '\334', '\272', '\230', '\166', '\124', '\062', '\020' } +}; diff --git a/numpy/core/code_generators/genapi.py b/numpy/core/code_generators/genapi.py index 68ae30d5b..c23fd6c72 100644 --- a/numpy/core/code_generators/genapi.py +++ b/numpy/core/code_generators/genapi.py @@ -6,17 +6,35 @@ See ``find_function`` for how functions should be formatted, and specified. """ -from numpy.distutils.conv_template import process_file as process_c_file - import hashlib import io import os import re import sys import textwrap +import importlib.util from os.path import join + +def get_processor(): + # Convoluted because we can't import from numpy.distutils + # (numpy is not yet built) + conv_template_path = os.path.join( + os.path.dirname(__file__), + '..', '..', 'distutils', 'conv_template.py' + ) + spec = importlib.util.spec_from_file_location( + 'conv_template', conv_template_path + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.process_file + + +process_c_file = get_processor() + + __docformat__ = 'restructuredtext' # The files under src/ that are scanned for API functions diff --git a/numpy/core/code_generators/generate_numpy_api.py b/numpy/core/code_generators/generate_numpy_api.py index a57a36a78..bdfd635e4 100644 --- a/numpy/core/code_generators/generate_numpy_api.py +++ b/numpy/core/code_generators/generate_numpy_api.py @@ -1,6 +1,8 @@ +#!/usr/bin/env python3 import os -import genapi +import argparse +import genapi from genapi import \ TypeApi, GlobalVarApi, FunctionApi, BoolValuesApi @@ -242,3 +244,29 @@ def do_generate_api(targets, sources): genapi.write_file(doc_file, s) return targets + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-o", + "--outdir", + type=str, + help="Path to the output directory" + ) + parser.add_argument( + "-i", + "--ignore", + type=str, + help="An ignored input - may be useful to add a " + "dependency between custom targets" + ) + args = parser.parse_args() + + outdir_abs = os.path.join(os.getcwd(), args.outdir) + + generate_api(outdir_abs) + + +if __name__ == "__main__": + main() diff --git a/numpy/core/code_generators/generate_ufunc_api.py b/numpy/core/code_generators/generate_ufunc_api.py index 04c023675..5b64c8217 100644 --- a/numpy/core/code_generators/generate_ufunc_api.py +++ b/numpy/core/code_generators/generate_ufunc_api.py @@ -1,9 +1,9 @@ import os -import genapi - -import numpy_api +import argparse +import genapi from genapi import TypeApi, FunctionApi +import numpy_api h_template = r""" #ifdef _UMATHMODULE @@ -191,3 +191,22 @@ NumPy Ufunc C-API genapi.write_file(doc_file, s) return targets + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-o", + "--outdir", + type=str, + help="Path to the output directory" + ) + args = parser.parse_args() + + outdir_abs = os.path.join(os.getcwd(), args.outdir) + + generate_api(outdir_abs) + + +if __name__ == "__main__": + main() diff --git a/numpy/core/code_generators/generate_umath.py b/numpy/core/code_generators/generate_umath.py index 39d4497b5..40382b8ae 100644 --- a/numpy/core/code_generators/generate_umath.py +++ b/numpy/core/code_generators/generate_umath.py @@ -3,6 +3,7 @@ import re import struct import sys import textwrap +import argparse Zero = "PyLong_FromLong(0)" One = "PyLong_FromLong(1)" @@ -1224,8 +1225,29 @@ def make_code(funcdict, filename): return code -if __name__ == "__main__": +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-o", + "--outfile", + type=str, + help="Path to the output directory" + ) + args = parser.parse_args() + + # used to insert the name of this file into the generated file filename = __file__ code = make_code(defdict, filename) - with open('__umath_generated.c', 'w') as fid: - fid.write(code) + + if not args.outfile: + # This is the distutils-based build + outfile = '__umath_generated.c' + else: + outfile = os.path.join(os.getcwd(), args.outfile) + + with open(outfile, 'w') as f: + f.write(code) + + +if __name__ == "__main__": + main() diff --git a/numpy/core/code_generators/generate_umath_doc.py b/numpy/core/code_generators/generate_umath_doc.py index 9888730fd..fc0c2a138 100644 --- a/numpy/core/code_generators/generate_umath_doc.py +++ b/numpy/core/code_generators/generate_umath_doc.py @@ -1,6 +1,7 @@ import sys import os import textwrap +import argparse sys.path.insert(0, os.path.dirname(__file__)) import ufunc_docstrings as docstrings @@ -28,3 +29,21 @@ def write_code(target): cdef_str = normalize_doc(string) fid.write(f"#define {cdef_name} \"{cdef_str}\"\n") fid.write("#endif //NUMPY_CORE_INCLUDE__UMATH_DOC_GENERATED_H\n") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-o", + "--outfile", + type=str, + help="Path to the output directory" + ) + args = parser.parse_args() + + outfile = os.path.join(os.getcwd(), args.outfile) + write_code(outfile) + + +if __name__ == '__main__': + main() diff --git a/numpy/core/code_generators/numpy_api.py b/numpy/core/code_generators/numpy_api.py index fa28f92b7..6b9080403 100644 --- a/numpy/core/code_generators/numpy_api.py +++ b/numpy/core/code_generators/numpy_api.py @@ -13,7 +13,23 @@ When adding a function, make sure to use the next integer not used as an index exception, so it should hopefully not get unnoticed). """ -from code_generators.genapi import StealRef + +import os +import importlib.util + + +def get_StealRef(): + # Convoluted because we can't import from numpy.distutils + # (numpy is not yet built) + genapi_py = os.path.join(os.path.dirname(__file__), 'genapi.py') + spec = importlib.util.spec_from_file_location('conv_template', genapi_py) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod.StealRef + + +StealRef = get_StealRef() +#from code_generators.genapi import StealRef # index, type multiarray_global_vars = { diff --git a/numpy/core/code_generators/verify_c_api_version.py b/numpy/core/code_generators/verify_c_api_version.py new file mode 100644 index 000000000..282e5e7d3 --- /dev/null +++ b/numpy/core/code_generators/verify_c_api_version.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +import os +import sys +import argparse + + +class MismatchCAPIError(ValueError): + pass + + +def get_api_versions(apiversion): + """ + Return current C API checksum and the recorded checksum. + + Return current C API checksum and the recorded checksum for the given + version of the C API version. + + """ + # Compute the hash of the current API as defined in the .txt files in + # code_generators + sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) + try: + m = __import__('genapi') + numpy_api = __import__('numpy_api') + curapi_hash = m.fullapi_hash(numpy_api.full_api) + apis_hash = m.get_versions_hash() + finally: + del sys.path[0] + + return curapi_hash, apis_hash[apiversion] + + +def check_api_version(apiversion): + """Emits a MismatchCAPIWarning if the C API version needs updating.""" + curapi_hash, api_hash = get_api_versions(apiversion) + + # If different hash, it means that the api .txt files in + # codegen_dir have been updated without the API version being + # updated. Any modification in those .txt files should be reflected + # in the api and eventually abi versions. + # To compute the checksum of the current API, use numpy/core/cversions.py + if not curapi_hash == api_hash: + msg = ("API mismatch detected, the C API version " + "numbers have to be updated. Current C api version is " + f"{apiversion}, with checksum {curapi_hash}, but recorded " + f"checksum in core/codegen_dir/cversions.txt is {api_hash}. If " + "functions were added in the C API, you have to update " + f"C_API_VERSION in {__file__}." + ) + raise MismatchCAPIError(msg) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--api-version", + type=str, + help="C API version to verify (as a hex string)" + ) + args = parser.parse_args() + + check_api_version(int(args.api_version, base=16)) + + +if __name__ == "__main__": + main() diff --git a/numpy/core/config.h.in b/numpy/core/config.h.in new file mode 100644 index 000000000..f4ccdcd94 --- /dev/null +++ b/numpy/core/config.h.in @@ -0,0 +1,118 @@ +#mesondefine SIZEOF_PY_INTPTR_T +#mesondefine SIZEOF_OFF_T +#mesondefine SIZEOF_PY_LONG_LONG + +#mesondefine HAVE_BACKTRACE +#mesondefine HAVE_MADVISE +#mesondefine HAVE_FTELLO +#mesondefine HAVE_FSEEKO +#mesondefine HAVE_FALLOCATE +#mesondefine HAVE_STRTOLD_L +#mesondefine HAVE__THREAD +#mesondefine HAVE___DECLSPEC_THREAD_ + +/* Optional headers */ +#mesondefine HAVE_XLOCALE_H +#mesondefine HAVE_DLFCN_H +#mesondefine HAVE_EXECINFO_H +#mesondefine HAVE_LIBUNWIND_H +#mesondefine HAVE_SYS_MMAN_H +#mesondefine HAVE_XMMINTRIN_H +#mesondefine HAVE_EMMINTRIN_H +#mesondefine HAVE_IMMINTRIN_H + +/* Optional intrinsics */ +#mesondefine HAVE___BUILTIN_ISNAN +#mesondefine HAVE___BUILTIN_ISINF +#mesondefine HAVE___BUILTIN_ISFINITE +#mesondefine HAVE___BUILTIN_BSWAP32 +#mesondefine HAVE___BUILTIN_BSWAP64 +#mesondefine HAVE___BUILTIN_EXPECT +#mesondefine HAVE___BUILTIN_MUL_OVERFLOW +#mesondefine HAVE__M_FROM_INT64 +#mesondefine HAVE__MM_LOAD_PS +#mesondefine HAVE__MM_PREFETCH +#mesondefine HAVE__MM_LOAD_PD +#mesondefine HAVE___BUILTIN_PREFETCH +#mesondefine HAVE_LINK_AVX +#mesondefine HAVE_LINK_AVX2 +#mesondefine HAVE_LINK_AVX512F +#mesondefine HAVE_LINK_AVX512_SKX +#mesondefine HAVE_XGETBV + +#mesondefine HAVE_ATTRIBUTE_OPTIMIZE_UNROLL_LOOPS +#mesondefine HAVE_ATTRIBUTE_OPTIMIZE_OPT_3 +#mesondefine HAVE_ATTRIBUTE_OPTIMIZE_OPT_2 +#mesondefine HAVE_ATTRIBUTE_NONNULL +#mesondefine HAVE_ATTRIBUTE_TARGET_AVX +#mesondefine HAVE_ATTRIBUTE_TARGET_AVX2 +#mesondefine HAVE_ATTRIBUTE_TARGET_AVX512F +#mesondefine HAVE_ATTRIBUTE_TARGET_AVX512_SKX +#mesondefine HAVE_ATTRIBUTE_TARGET_AVX2_WITH_INTRINSICS +#mesondefine HAVE_ATTRIBUTE_TARGET_AVX512F_WITH_INTRINSICS +#mesondefine HAVE_ATTRIBUTE_TARGET_AVX512_SKX_WITH_INTRINSICS + +#mesondefine HAVE_DECL_ISNAN +#mesondefine HAVE_DECL_ISINF +#mesondefine HAVE_DECL_ISFINITE +#mesondefine HAVE_DECL_SIGNBIT + +/* C99 complex support and complex.h are not universal */ +#mesondefine HAVE_COMPLEX_H +#mesondefine HAVE_CABS +#mesondefine HAVE_CACOS +#mesondefine HAVE_CACOSH +#mesondefine HAVE_CARG +#mesondefine HAVE_CASIN +#mesondefine HAVE_CASINH +#mesondefine HAVE_CATAN +#mesondefine HAVE_CATANH +#mesondefine HAVE_CEXP +#mesondefine HAVE_CLOG +#mesondefine HAVE_CPOW +#mesondefine HAVE_CSQRT +#mesondefine HAVE_CABSF +#mesondefine HAVE_CACOSF +#mesondefine HAVE_CACOSHF +#mesondefine HAVE_CARGF +#mesondefine HAVE_CASINF +#mesondefine HAVE_CASINHF +#mesondefine HAVE_CATANF +#mesondefine HAVE_CATANHF +#mesondefine HAVE_CEXPF +#mesondefine HAVE_CLOGF +#mesondefine HAVE_CPOWF +#mesondefine HAVE_CSQRTF +#mesondefine HAVE_CABSL +#mesondefine HAVE_CACOSL +#mesondefine HAVE_CACOSHL +#mesondefine HAVE_CARGL +#mesondefine HAVE_CASINL +#mesondefine HAVE_CASINHL +#mesondefine HAVE_CATANL +#mesondefine HAVE_CATANHL +#mesondefine HAVE_CEXPL +#mesondefine HAVE_CLOGL +#mesondefine HAVE_CPOWL +#mesondefine HAVE_CSQRTL + +#mesondefine NPY_CAN_LINK_SVML +#mesondefine NPY_RELAXED_STRIDES_DEBUG + +#mesondefine HAVE_LDOUBLE_INTEL_EXTENDED_16_BYTES_LE +#mesondefine HAVE_LDOUBLE_INTEL_EXTENDED_12_BYTES_LE +#mesondefine HAVE_LDOUBLE_MOTOROLA_EXTENDED_12_BYTES_BE +#mesondefine HAVE_LDOUBLE_IEEE_DOUBLE_LE +#mesondefine HAVE_LDOUBLE_IEEE_DOUBLE_BE +#mesondefine HAVE_LDOUBLE_IEEE_QUAD_LE +#mesondefine HAVE_LDOUBLE_IEEE_QUAD_BE +#mesondefine HAVE_LDOUBLE_IBM_DOUBLE_DOUBLE_LE +#mesondefine HAVE_LDOUBLE_IBM_DOUBLE_DOUBLE_BE + +#ifndef __cplusplus +/* #undef inline */ +#endif + +#ifndef NUMPY_CORE_SRC_COMMON_NPY_CONFIG_H_ +#error config.h should never be included directly, include npy_config.h instead +#endif diff --git a/numpy/core/include/meson.build b/numpy/core/include/meson.build new file mode 100644 index 000000000..0f9fbe76b --- /dev/null +++ b/numpy/core/include/meson.build @@ -0,0 +1,51 @@ +installed_headers = [ + 'numpy/_neighborhood_iterator_imp.h', + 'numpy/arrayobject.h', + 'numpy/arrayscalars.h', + 'numpy/experimental_dtype_api.h', + 'numpy/halffloat.h', + 'numpy/ndarrayobject.h', + 'numpy/ndarraytypes.h', + 'numpy/noprefix.h', + 'numpy/npy_1_7_deprecated_api.h', + 'numpy/npy_3kcompat.h', + 'numpy/npy_common.h', + 'numpy/npy_cpu.h', + 'numpy/npy_endian.h', + 'numpy/npy_interrupt.h', + 'numpy/npy_math.h', + 'numpy/npy_no_deprecated_api.h', + 'numpy/npy_os.h', + 'numpy/numpyconfig.h', + 'numpy/old_defines.h', + 'numpy/ufuncobject.h', + 'numpy/utils.h', +] + +# Note that oldnumeric.h is not installed on purpose. After investigation, +# there are only two active repos left which use it, and those got a heads up +# about removal of this header in numpy 1.25.0: +# https://github.com/enthought/enable/issues/1005 +# https://github.com/fhs/pyhdf/issues/63 + +py.install_sources( + installed_headers, + subdir: 'numpy/core/include/numpy' +) + +py.install_sources( + [ + 'numpy/random/bitgen.h', + 'numpy/random/distributions.h', + ], + subdir: 'numpy/core/include/numpy/random' +) + + +py.install_sources( + [ + 'numpy/libdivide/libdivide.h', + 'numpy/libdivide/LICENSE.txt', + ], + subdir: 'numpy/core/include/numpy/random' +) diff --git a/numpy/core/include/numpy/_numpyconfig.h.in b/numpy/core/include/numpy/_numpyconfig.h.in new file mode 100644 index 000000000..8e799971a --- /dev/null +++ b/numpy/core/include/numpy/_numpyconfig.h.in @@ -0,0 +1,36 @@ +#mesondefine NPY_HAVE_ENDIAN_H + +#mesondefine NPY_SIZEOF_SHORT +#mesondefine NPY_SIZEOF_INT +#mesondefine NPY_SIZEOF_LONG +#mesondefine NPY_SIZEOF_FLOAT +#mesondefine NPY_SIZEOF_COMPLEX_FLOAT +#mesondefine NPY_SIZEOF_DOUBLE +#mesondefine NPY_SIZEOF_COMPLEX_DOUBLE +#mesondefine NPY_SIZEOF_LONGDOUBLE +#mesondefine NPY_SIZEOF_COMPLEX_LONGDOUBLE +#mesondefine NPY_SIZEOF_PY_INTPTR_T +#mesondefine NPY_SIZEOF_OFF_T +#mesondefine NPY_SIZEOF_PY_LONG_LONG +#mesondefine NPY_SIZEOF_LONGLONG + +#mesondefine NPY_HAVE_DECL_ISNAN +#mesondefine NPY_HAVE_DECL_ISINF +#mesondefine NPY_HAVE_DECL_ISFINITE +#mesondefine NPY_HAVE_DECL_SIGNBIT +#mesondefine NPY_USE_C99_COMPLEX +#mesondefine NPY_HAVE_COMPLEX_DOUBLE +#mesondefine NPY_HAVE_COMPLEX_FLOAT +#mesondefine NPY_HAVE_COMPLEX_LONG_DOUBLE +#mesondefine NPY_USE_C99_FORMATS + +#mesondefine NPY_NO_SIGNAL +#mesondefine NPY_NO_SMP + +#mesondefine NPY_VISIBILITY_HIDDEN +#mesondefine NPY_ABI_VERSION +#mesondefine NPY_API_VERSION + +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS 1 +#endif diff --git a/numpy/core/meson.build b/numpy/core/meson.build new file mode 100644 index 000000000..507d0868d --- /dev/null +++ b/numpy/core/meson.build @@ -0,0 +1,929 @@ +# This file should contain what setup.py + setup_common.py do (WIP) +# +# Potential issues to address or keep track of: +# - sincos detection incorrect on NetBSD: https://github.com/mesonbuild/meson/issues/10641 + +# Versioning support +#------------------- +# +# How to change C_API_VERSION ? +# - increase C_API_VERSION value +# - record the hash for the new C API with the cversions.py script +# and add the hash to cversions.txt +# The hash values are used to remind developers when the C API number was not +# updated - generates a MismatchCAPIWarning warning which is turned into an +# exception for released version. + +# Binary compatibility version number. This number is increased whenever the +# C-API is changed such that binary compatibility is broken, i.e. whenever a +# recompile of extension modules is needed. +C_ABI_VERSION = '0x01000009' + +# Minor API version. This number is increased whenever a change is made to the +# C-API -- whether it breaks binary compatibility or not. Some changes, such +# as adding a function pointer to the end of the function table, can be made +# without breaking binary compatibility. In this case, only the C_API_VERSION +# (*not* C_ABI_VERSION) would be increased. Whenever binary compatibility is +# broken, both C_API_VERSION and C_ABI_VERSION should be increased. +# +# The version needs to be kept in sync with that in cversions.txt. +# +# 0x00000008 - 1.7.x +# 0x00000009 - 1.8.x +# 0x00000009 - 1.9.x +# 0x0000000a - 1.10.x +# 0x0000000a - 1.11.x +# 0x0000000a - 1.12.x +# 0x0000000b - 1.13.x +# 0x0000000c - 1.14.x +# 0x0000000c - 1.15.x +# 0x0000000d - 1.16.x +# 0x0000000d - 1.19.x +# 0x0000000e - 1.20.x +# 0x0000000e - 1.21.x +# 0x0000000f - 1.22.x +# 0x00000010 - 1.23.x +# 0x00000010 - 1.24.x +C_API_VERSION = '0x00000010' + +# Check whether we have a mismatch between the set C API VERSION and the +# actual C API VERSION. Will raise a MismatchCAPIError if so. +run_command('code_generators/verify_c_api_version.py', '--api-version', C_API_VERSION, check: true) + + +# Generate config.h and _numpyconfig.h +# ------------------------------------ +# +# There are two generated headers: +# - config.h: a private header, which is not installed and used only at +# build time, mostly to determine whether or not optional +# headers, compiler attributes, etc. are available +# - _numpyconfig.h: a header with public `NPY_` symbols which get made +# available via numpyconfig.h +# +# Note that `HAVE_*` symbols indicate the presence or absence of a checked +# property of the build environment. When available, these symbols get defined +# to `1`; when not available they must not be defined at all. This is +# important, because code in NumPy typically does not check the value but only +# whether the symbol is defined. So `#define HAVE_SOMETHING 0` is wrong. + +cdata = configuration_data() + +cdata.set('NPY_ABI_VERSION', C_ABI_VERSION) +cdata.set('NPY_API_VERSION', C_API_VERSION) + +use_svml = ( + host_machine.system() == 'linux' and + host_machine.cpu_family() == 'x86_64' and + not get_option('disable-svml') +) +if use_svml + cdata.set10('NPY_CAN_LINK_SVML', true) + if not fs.exists('src/umath/svml') + error('Missing the `SVML` git submodule! Run `git submodule update --init` to fix this.') + endif +endif + +# Check sizes of types. Note, some of these landed in config.h before, but were +# unused. So clean that up and only define the NPY_SIZEOF flavors rather than +# the SIZEOF ones +types_to_check = [ + ['NPY_SIZEOF_SHORT', 'short'], + ['NPY_SIZEOF_INT', 'int'], + ['NPY_SIZEOF_LONG', 'long'], + ['NPY_SIZEOF_LONGLONG', 'long long'], + ['NPY_SIZEOF_FLOAT', 'float'], + ['NPY_SIZEOF_DOUBLE', 'double'], + ['NPY_SIZEOF_LONGDOUBLE', 'long double'], +] +foreach symbol_type: types_to_check + cdata.set(symbol_type[0], cc.sizeof(symbol_type[1])) +endforeach +cdata.set('NPY_SIZEOF_OFF_T', cc.sizeof('off_t', prefix: '#include <sys/types.h>')) +cdata.set('NPY_SIZEOF_PY_INTPTR_T', + cc.sizeof('Py_intptr_t', dependencies: py_dep, prefix: '#include <Python.h>')) +cdata.set('NPY_SIZEOF_PY_LONG_LONG', + cc.sizeof('PY_LONG_LONG', dependencies: py_dep, prefix: '#include <Python.h>')) + +# Check for complex support +if cc.has_header('complex.h') + cdata.set10('HAVE_COMPLEX_H', true) + cdata.set10('NPY_USE_C99_COMPLEX', true) + complex_types_to_check = [ + ['NPY_HAVE_COMPLEX_FLOAT', 'NPY_SIZEOF_COMPLEX_FLOAT', 'complex float', 'float'], + ['NPY_HAVE_COMPLEX_DOUBLE', 'NPY_SIZEOF_COMPLEX_DOUBLE', 'complex double', 'double'], + ['NPY_HAVE_COMPLEX_LONG_DOUBLE', 'NPY_SIZEOF_COMPLEX_LONGDOUBLE', 'complex long double', 'long double'], + ] + foreach symbol_type: complex_types_to_check + if cc.has_type(symbol_type[2], prefix: '#include <complex.h>') + cdata.set10(symbol_type[0], true) + # Determine size of NumPy's own, struct-based, complex types. Binary + # compatibility with C99 complex types is checked at build time in `npy_common.h`. + ftype = symbol_type[3] + cdata.set(symbol_type[1], cc.sizeof(f'struct {@ftype@ __x; @ftype@ __y;}')) + endif + endforeach +endif + +# Mandatory functions: if not found, fail the build +# Some of these can still be blocklisted if the C99 implementation +# is buggy, see numpy/core/src/common/npy_config.h +mandatory_funcs = [ + 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'fabs', + 'floor', 'ceil', 'sqrt', 'log10', 'log', 'exp', 'asin', + 'acos', 'atan', 'fmod', 'modf', 'frexp', 'ldexp', + 'expm1', 'log1p', 'acosh', 'asinh', 'atanh', + 'rint', 'trunc', 'exp2', + 'copysign', 'nextafter', 'strtoll', 'strtoull', 'cbrt', + 'log2', 'pow', 'hypot', 'atan2', + 'csin', 'csinh', 'ccos', 'ccosh', 'ctan', 'ctanh', + 'creal', 'cimag', 'conj' +] +foreach func: mandatory_funcs + if not cc.has_function(func) + error('Function `{func}` not found') + endif +endforeach + +c99_complex_funcs = [ + 'cabs', 'cacos', 'cacosh', 'carg', 'casin', 'casinh', 'catan', + 'catanh', 'cexp', 'clog', 'cpow', 'csqrt' +] +foreach func: c99_complex_funcs + func_single = func + 'f' + func_longdouble = func + 'l' + if cc.has_function(func) + cdata.set10('HAVE_' + func.to_upper(), true) + endif + if cc.has_function(func_single) + cdata.set10('HAVE_' + func_single.to_upper(), true) + endif + if cc.has_function(func_longdouble) + cdata.set10('HAVE_' + func_longdouble.to_upper(), true) + endif +endforeach + +# We require C99 so these should always be found at build time. But for +# libnpymath as a C99 compat layer, these may still be relevant. +c99_macros = ['isfinite', 'isinf', 'isnan', 'signbit'] +foreach macro: c99_macros + if cc.has_function(macro) + cdata.set10('NPY_HAVE_DECL_' + macro.to_upper(), true) + if not cc.has_header_symbol('Python.h', macro, dependencies: py_dep) + # Add in config.h as well, except if it will clash with Python's config.h content + cdata.set10('HAVE_DECL_' + macro.to_upper(), true) + endif + endif +endforeach + +# variable attributes tested via "int %s a" % attribute +optional_variable_attributes = [ + ['__thread', 'HAVE__THREAD'], + ['__declspec(thread)', 'HAVE___DECLSPEC_THREAD_'] +] +foreach optional_attr: optional_variable_attributes + attr = optional_attr[0] + code = f''' + #pragma GCC diagnostic error "-Wattributes" + #pragma clang diagnostic error "-Wattributes" + + int @attr@ foo; + ''' + code += ''' + int + main() + { + return 0; + } + ''' + if cc.compiles(code) + cdata.set10(optional_attr[1], true) + endif +endforeach + +inc_curdir = include_directories('.') +optional_file_funcs = ['fallocate', 'ftello', 'fseeko'] +foreach filefunc_maybe: optional_file_funcs + config_value = 'HAVE_' + filefunc_maybe.to_upper() + # Some functions may already have HAVE_* defined by `Python.h`. Python puts + # its config.h in the public Python.h namespace, so we have a possible clash + # for the common functions we test. Hence we skip those checks. + if (filefunc_maybe == 'fallocate' or + not cc.has_header_symbol('Python.h', config_value, dependencies: py_dep) + ) + if cc.has_function(filefunc_maybe, + include_directories: inc_curdir, + prefix: '#include "feature_detection_stdio.h"' + ) + cdata.set10(config_value, true) + endif + endif +endforeach + +# Optional locale function +have_strtold_l = cc.has_function('strtold_l', include_directories: inc_curdir, + prefix:''' + #include <stdlib.h> + #include <xlocale.h> + #include "feature_detection_locale.h" +''') +if not have_strtold_l + # Retry with locale.h, seems to vary across Linux distros + have_strtold_l = cc.has_function('strtold_l', include_directories: inc_curdir, + prefix:''' + #include <stdlib.h> + #include <locale.h> + #include "feature_detection_locale.h" + ''') +endif +if have_strtold_l + cdata.set10('HAVE_STRTOLD_L', true) +else + # FIXME: this is wrong! the HAVE_ define should not exist, or it'll be + # interpreted as the function being available (true/false does nothing, see + # note on HAVE_ defines higher up). This is necessary though in order to make + # the Linux CI job pass. So either the check is wrong somehow, or this + # function is not available in CI. For the latter there is a fallback path, + # but that is broken because we don't have the exact long double + # representation checks. + cdata.set10('HAVE_STRTOLD_L', false) +endif + +# Other optional functions +optional_misc_funcs = [ + 'backtrace', + 'madvise', +] +foreach func: optional_misc_funcs + if cc.has_function(func, + include_directories: inc_curdir, + prefix: '#include "feature_detection_misc.h"' + ) + cdata.set10('HAVE_' + func.to_upper(), true) + endif +endforeach + +# SSE headers only enabled automatically on amd64/x32 builds +optional_headers = [ + 'xmmintrin.h', # SSE + 'emmintrin.h', # SSE2 + 'immintrin.h', # AVX + 'features.h', # for glibc version linux + 'xlocale.h', # see GH#8367 + 'dlfcn.h', # dladdr + 'execinfo.h', # backtrace + 'libunwind.h', # backtrace for LLVM/Clang using libunwind + 'sys/mman.h', # madvise +] +foreach header: optional_headers + if cc.has_header(header) + cdata.set10('HAVE_' + header.to_upper().replace('.', '_').replace('/', '_'), true) + endif +endforeach + +# Optional compiler attributes +# TODO: this doesn't work with cc.has_function_attribute, see +# https://github.com/mesonbuild/meson/issues/10732 +optional_function_attributes = [ + ['optimize("unroll-loops")', 'OPTIMIZE_UNROLL_LOOPS'], + ['optimize("O3")', 'OPTIMIZE_OPT_3'], + ['optimize("O2")', 'OPTIMIZE_OPT_2'], + ['optimize("nonnull (1)")', 'NONNULL'], + ] +if host_machine.cpu_family() in ['x86', 'x86_64'] + optional_function_attributes += [ + ['target("avx")', 'TARGET_AVX'], + ['target("avx2")', 'TARGET_AVX2'], + ['target("avx512f")', 'TARGET_AVX512F'], + ['target("avx512f,avx512dq,avx512bw,avx512vl,avx512cd")', 'TARGET_AVX512_SKX'], + ] + # TODO: add the _WITH_INTRINSICS_AVX list +endif +#foreach attr: optional_function_attributes +# if cc.has_function_attribute(attr[0]) +# cdata.set10('HAVE_ATTRIBUTE_' + attr[1], true) +# endif +#endforeach + +# Optional GCC compiler builtins and their call arguments. +# If given, a required header and definition name (HAVE_ prepended) +# Call arguments are required as the compiler will do strict signature checking +optional_intrinsics = [ + ['__builtin_isnan', '5.', [], []], + ['__builtin_isinf', '5.', [], []], + ['__builtin_isfinite', '5.', [], []], + ['__builtin_bswap32', '5u', [], []], + ['__builtin_bswap64', '5u', [], []], + ['__builtin_expect', '5, 0', [], []], + ['__builtin_mul_overflow', '5, 5, (int*)5', [], []], +] +if host_machine.cpu_family() in ['x86', 'x86_64'] + optional_intrinsics += [ + # MMX only needed for icc, but some clang's don't have it + ['_m_from_int64', '0', ['emmintrin.h'], []], + ['_mm_load_ps', '(float*)0', ['xmmintrin.h'], []], # SSE + ['_mm_prefetch', '(float*)0, _MM_HINT_NTA', ['xmmintrin.h'], []], # SSE + ['_mm_load_pd', '(double*)0', ['emmintrin.h'], []], # SSE2 + ['__builtin_prefetch', '(float*)0, 0, 3', [], []], + # Check that the linker can handle AVX + ['__asm__ volatile', '"vpand %xmm1, %xmm2, %xmm3"', ['stdio.h'], ['HAVE_LINK_AVX']], + ['__asm__ volatile', '"vpand %ymm1, %ymm2, %ymm3"', ['stdio.h'], ['HAVE_LINK_AVX2']], + ['__asm__ volatile', '"vpaddd %zmm1, %zmm2, %zmm3"', ['stdio.h'], ['HAVE_LINK_AVX512F']], + ['__asm__ volatile', + '"vfpclasspd $0x40, %zmm15, %k6\\n vmovdqu8 %xmm0, %xmm1\\n vpbroadcastmb2q %k0, %xmm0"', + ['stdio.h'], ['HAVE_LINK_AVX512_SKX'] + ], + ['__asm__ volatile', '"xgetbv"', ['stdio.h'], ['HAVE_XGETBV']], + ] +endif +foreach intrin: optional_intrinsics + func = intrin[0] + func_args = intrin[1] + header = intrin[2] + define = intrin[3] + code = '' + if header.length() == 1 + header_name = header[0] + code += f'#include <@header_name@>' + endif + code += f''' + #ifdef _MSC_VER + #pragma function(@func@) + #endif + int main(void) { + @func@(@func_args@); + return 0; + }; + ''' + if define.length() == 1 + define_name = define[0] + else + define_name = 'HAVE_' + func.to_upper() + endif + if cc.links(code) + cdata.set10(define_name, true) + endif +endforeach + +# long double representation detection (see setup_common.py) +# TODO: this is still incomplete, and different from how it's done in the +# numpy.distutils based build, see https://github.com/mesonbuild/meson/issues/11068 +longdouble_size = cc.sizeof('long double') +if longdouble_size == 8 + if host_machine.endian() == 'little' + longdouble_format = 'IEEE_DOUBLE_LE' + else + longdouble_format = 'IEEE_DOUBLE_BE' + endif +elif longdouble_size == 12 + error('This should not be possible, 12 bits of "content" should still result in sizeof() being 16. Please report this error!' + ) +elif longdouble_size == 16 + if host_machine.endian() == 'little' + # FIXME: this varies, there's multiple formats here! Not yet implemented. + # TBD how we deal with the mess of old long double formats. + longdouble_format = 'INTEL_EXTENDED_16_BYTES_LE' + else + error('No idea what this is ....') + endif +else + error('Unknown long double size: ' + londouble_size) +endif +cdata.set10('HAVE_LDOUBLE_' + longdouble_format, true) + +if cc.has_header('endian.h') + cdata.set10('NPY_HAVE_ENDIAN_H', true) +endif +if cc.has_header('sys/endian.h') + cdata.set10('NPY_HAVE_SYS_ENDIAN_H', true) +endif +if is_windows + cdata.set10('NPY_NO_SIGNAL', true) +endif +# Command-line switch; distutils build checked for `NPY_NOSMP` env var instead +# TODO: document this (search for NPY_NOSMP in C API docs) +cdata.set10('NPY_NO_SMP', get_option('disable-threading')) + +# Use bogus stride debug aid to flush out bugs where users use strides of +# dimensions with length 1 to index a full contiguous array. +cdata.set10('NPY_RELAXED_STRIDES_DEBUG', get_option('relaxed-strides-debug')) + +# Check whether we can use inttypes (C99) formats +if cc.has_header_symbol('inttypes.h', 'PRIdPTR') + cdata.set10('NPY_USE_C99_FORMATS', true) +endif + +visibility_hidden = '' +if cc.has_function_attribute('visibility:hidden') + visibility_hidden = '__attribute__((visibility("hidden")))' +endif +cdata.set('NPY_VISIBILITY_HIDDEN', visibility_hidden) + + +config_h = configure_file( + input: 'config.h.in', + output: 'config.h', + configuration: cdata, + install: false +) + +_numpyconfig_h = configure_file( + input: 'include/numpy/_numpyconfig.h.in', + output: '_numpyconfig.h', + configuration: cdata, + install: true, + install_dir: np_dir / 'core/include/numpy' +) + +# Build npymath static library +# ---------------------------- + +staticlib_cflags = [] +if cc.get_id() == 'msvc' + # Disable voltbl section for vc142 to allow link using mingw-w64; see: + # https://github.com/matthew-brett/dll_investigation/issues/1#issuecomment-1100468171 + # Needs to be added to static libraries that are shipped for reuse (i.e., + # libnpymath and libnpyrandom) + if cc.has_argument('-d2VolatileMetadata-') + staticlib_cflags += '-d2VolatileMetadata-' + endif +endif + +npy_math_internal_h = custom_target( + output: 'npy_math_internal.h', + input: 'src/npymath/npy_math_internal.h.src', + command: [src_file_cli, '@INPUT@', '-o', '@OUTPUT@'], +) + +npymath_sources = [ + src_file.process('src/npymath/ieee754.c.src'), + src_file.process('src/npymath/npy_math_complex.c.src'), + npy_math_internal_h, + 'src/npymath/_signbit.c', + 'src/npymath/halffloat.c', + 'src/npymath/npy_math.c', +] +npymath_lib = static_library('npymath', + npymath_sources, + c_args: staticlib_cflags, + include_directories: ['include', 'src/npymath', 'src/common'], + dependencies: py_dep, + install: true, + install_dir: np_dir / 'core/lib', +) + +dir_separator = '/' +if build_machine.system() == 'windows' + dir_separator = '\\' +endif +configure_file( + input: 'npymath.ini.in', + output: 'npymath.ini', + configuration: configuration_data({ + 'pkgname' : 'numpy.core', + 'sep' : dir_separator, + }), + install: true, + install_dir: np_dir / 'core/lib/npy-pkg-config' +) +configure_file( + input: 'mlib.ini.in', + output: 'mlib.ini', + configuration: configuration_data({ + 'posix_mathlib' : mlib_linkflag, + 'msvc_mathlib' : 'm.lib', + }), + install: true, + install_dir: np_dir / 'core/lib/npy-pkg-config' +) + +if false + # This doesn't quite work (yet), it assumes we'll install headers under + # include/, and trying to add the correct path with `extra_cflags` runs into + # not being able to get a path relative to the prefix. + # Note that installing numpy headers under include/ would be a good idea, but + # that needs work (and may run into trouble with wheels perhaps, not quite + # clear if they allow this). + pkg = import('pkgconfig') + pkg.generate(npymath_lib, + name: 'npymath', + description: 'Portable, core math library implementing C99 standard', + url: 'https://github.com/numpy/numpy', + requires: 'numpy', + install_dir: np_dir / 'core/lib/npy-pkg-config', + extra_cflags: '-I${includedir}' + np_dir / 'core' / 'include', + ) +endif + +# Generate NumPy C API sources +# ---------------------------- + +# This is a single C file. It needs to be built before _multiarray_umath starts +# building, but we can't add it to the sources of that extension (this C file +# doesn't compile, it's only included in another r file. Hence use the slightly +# hacky --ignore argument to the next custom_target(). +src_umath_api_c = custom_target('__umath_generated', + output : '__umath_generated.c', + input : 'code_generators/generate_umath.py', + command: [py, '@INPUT@', '-o', '@OUTPUT@'], +) + +src_umath_doc_h = custom_target('_umath_doc_generated', + output : '_umath_doc_generated.h', + input : 'code_generators/generate_umath_doc.py', + command: [py, '@INPUT@', '-o', '@OUTPUT@'], +) + +src_numpy_api = custom_target('__multiarray_api', + output : ['__multiarray_api.c', '__multiarray_api.h', 'multiarray_api.txt'], + input : 'code_generators/generate_numpy_api.py', + command: [py, '@INPUT@', '-o', '@OUTDIR@', '--ignore', src_umath_api_c], + install: true, # NOTE: setup.py build installs all, but just need .h? + install_dir: np_dir / 'core/include/numpy' +) + +src_ufunc_api = custom_target('__ufunc_api', + output : ['__ufunc_api.c', '__ufunc_api.h', 'ufunc_api.txt'], + input : 'code_generators/generate_ufunc_api.py', + command: [py, '@INPUT@', '-o', '@OUTDIR@'], + install: true, # NOTE: setup.py build installs all, but just need .h? + install_dir: np_dir / 'core/include/numpy' +) + + +# Set common build flags for C and C++ code +# ----------------------------------------- + +# TODO: change to "feature" option in meson_options.txt? See +# https://mesonbuild.com/Build-options.html#build-options +disable_simd_optimizations = [] +if get_option('disable-simd-optimizations') + disable_simd_optimizations = '-DNPY_DISABLE_OPTIMIZATION' +endif + +# Common build flags +c_args_common = [ + '-DNPY_INTERNAL_BUILD', + '-DHAVE_NPY_CONFIG_H', + disable_simd_optimizations, + cflags_large_file_support, +] + +# Same as NPY_CXX_FLAGS (TODO: extend for what ccompiler_opt adds) +cpp_args_common = c_args_common + [ + '-D__STDC_VERSION__=0', # for compatibility with C headers + '-fno-exceptions', # no exception support + '-fno-rtti', # no runtime type information +] + +# Other submodules depend on generated headers and include directories from +# core, wrap those up into a reusable dependency. Also useful for some test +# modules in this build file. +np_core_dep = declare_dependency( + sources: [ + _numpyconfig_h, + npy_math_internal_h, + src_numpy_api[1], # __multiarray_api.h + src_ufunc_api[1], # __ufunc_api.h + ], + include_directories: [ + '.', + 'include', + 'src/common', + ] +) + + +# Build multiarray_tests module +# ----------------------------- +py.extension_module('_multiarray_tests', + [ + src_file.process('src/multiarray/_multiarray_tests.c.src'), + 'src/common/mem_overlap.c', + 'src/common/npy_argparse.c', + 'src/common/npy_hashtable.c', + src_file.process('src/common/templ_common.h.src') + ], + c_args: c_args_common, + include_directories: ['src/multiarray', 'src/npymath'], + dependencies: np_core_dep, + link_with: npymath_lib, + gnu_symbol_visibility: 'default', + install: true, + subdir: 'numpy/core', +) + +test_modules_src = [ + ['_umath_tests', [ + src_file.process('src/umath/_umath_tests.c.src'), + 'src/umath/_umath_tests.dispatch.c', + 'src/common/npy_cpu_features.c', + ]], + ['_rational_tests', 'src/umath/_rational_tests.c'], + ['_struct_ufunc_tests', 'src/umath/_struct_ufunc_tests.c'], + ['_operand_flag_tests', 'src/umath/_operand_flag_tests.c'], +] +foreach gen: test_modules_src + py.extension_module(gen[0], + gen[1], + c_args: c_args_common, + include_directories: ['src/multiarray', 'src/npymath'], + dependencies: np_core_dep, + install: true, + subdir: 'numpy/core', + ) +endforeach + +# Build _multiarray_umath module +# ------------------------------ +src_multiarray_umath_common = [ + 'src/common/array_assign.c', + 'src/common/mem_overlap.c', + 'src/common/npy_argparse.c', + 'src/common/npy_hashtable.c', + 'src/common/npy_longdouble.c', + 'src/common/ucsnarrow.c', + 'src/common/ufunc_override.c', + 'src/common/numpyos.c', + 'src/common/npy_cpu_features.c', + src_file.process('src/common/templ_common.h.src') +] +if have_blas + src_multiarray_umath_common += [ + 'src/common/cblasfuncs.c', + 'src/common/python_xerbla.c', + ] +endif + +src_multiarray = [ + 'src/multiarray/abstractdtypes.c', + 'src/multiarray/alloc.c', + src_file.process('src/multiarray/argfunc.dispatch.c.src'), + 'src/multiarray/arrayobject.c', + src_file.process('src/multiarray/arraytypes.h.src'), + 'src/multiarray/array_coercion.c', + 'src/multiarray/array_method.c', + 'src/multiarray/array_assign_scalar.c', + 'src/multiarray/array_assign_array.c', + 'src/multiarray/arrayfunction_override.c', + src_file.process('src/multiarray/arraytypes.c.src'), + 'src/multiarray/buffer.c', + 'src/multiarray/calculation.c', + 'src/multiarray/compiled_base.c', + 'src/multiarray/common.c', + 'src/multiarray/common_dtype.c', + 'src/multiarray/convert.c', + 'src/multiarray/convert_datatype.c', + 'src/multiarray/conversion_utils.c', + 'src/multiarray/ctors.c', + 'src/multiarray/datetime.c', + 'src/multiarray/datetime_strings.c', + 'src/multiarray/datetime_busday.c', + 'src/multiarray/datetime_busdaycal.c', + 'src/multiarray/descriptor.c', + 'src/multiarray/dlpack.c', + 'src/multiarray/dtypemeta.c', + 'src/multiarray/dragon4.c', + 'src/multiarray/dtype_transfer.c', + src_file.process('src/multiarray/einsum.c.src'), + src_file.process('src/multiarray/einsum_sumprod.c.src'), + 'src/multiarray/experimental_public_dtype_api.c', + 'src/multiarray/flagsobject.c', + 'src/multiarray/getset.c', + 'src/multiarray/hashdescr.c', + 'src/multiarray/item_selection.c', + 'src/multiarray/iterators.c', + 'src/multiarray/legacy_dtype_implementation.c', + src_file.process('src/multiarray/lowlevel_strided_loops.c.src'), + 'src/multiarray/mapping.c', + 'src/multiarray/methods.c', + 'src/multiarray/multiarraymodule.c', + 'src/multiarray/nditer_api.c', + 'src/multiarray/nditer_constr.c', + 'src/multiarray/nditer_pywrap.c', + src_file.process('src/multiarray/nditer_templ.c.src'), + 'src/multiarray/number.c', + 'src/multiarray/refcount.c', + src_file.process('src/multiarray/scalartypes.c.src'), + 'src/multiarray/sequence.c', + 'src/multiarray/shape.c', + 'src/multiarray/scalarapi.c', + 'src/multiarray/strfuncs.c', + 'src/multiarray/temp_elide.c', + 'src/multiarray/typeinfo.c', + 'src/multiarray/usertypes.c', + 'src/multiarray/vdot.c', + src_file.process('src/common/npy_sort.h.src'), + 'src/npysort/x86-qsort.dispatch.cpp', + 'src/npysort/quicksort.cpp', + 'src/npysort/mergesort.cpp', + 'src/npysort/timsort.cpp', + 'src/npysort/heapsort.cpp', + 'src/npysort/radixsort.cpp', + 'src/common/npy_partition.h', + 'src/npysort/selection.cpp', + 'src/common/npy_binsearch.h', + 'src/npysort/binsearch.cpp', + 'src/multiarray/textreading/conversions.c', + 'src/multiarray/textreading/field_types.c', + 'src/multiarray/textreading/growth.c', + 'src/multiarray/textreading/readtext.c', + 'src/multiarray/textreading/rows.c', + 'src/multiarray/textreading/stream_pyobject.c', + 'src/multiarray/textreading/str_to_int.c', + 'src/multiarray/textreading/tokenize.cpp', +] + +src_umath = [ + src_file.process('src/umath/funcs.inc.src'), + src_file.process('src/umath/loops.h.src'), + src_file.process('src/umath/loops_utils.h.src'), + src_file.process('src/umath/loops.c.src'), + src_file.process('src/umath/loops_arithm_fp.dispatch.c.src'), + src_file.process('src/umath/loops_arithmetic.dispatch.c.src'), + src_file.process('src/umath/loops_comparison.dispatch.c.src'), + src_file.process('src/umath/loops_exponent_log.dispatch.c.src'), + src_file.process('src/umath/loops_hyperbolic.dispatch.c.src'), + src_file.process('src/umath/loops_minmax.dispatch.c.src'), + src_file.process('src/umath/loops_modulo.dispatch.c.src'), + src_file.process('src/umath/loops_trigonometric.dispatch.c.src'), + src_file.process('src/umath/loops_umath_fp.dispatch.c.src'), + src_file.process('src/umath/loops_unary_fp.dispatch.c.src'), + src_file.process('src/umath/matmul.c.src'), + src_file.process('src/umath/matmul.h.src'), + src_file.process('src/umath/simd.inc.src'), + 'src/umath/ufunc_type_resolution.c', + 'src/umath/clip.cpp', + 'src/umath/clip.h', + 'src/umath/dispatching.c', + 'src/umath/extobj.c', + 'src/umath/legacy_array_method.c', + 'src/umath/override.c', + 'src/umath/reduction.c', + src_file.process('src/umath/scalarmath.c.src'), + 'src/umath/ufunc_object.c', + 'src/umath/umathmodule.c', + 'src/umath/string_ufuncs.cpp', + 'src/umath/wrapping_array_method.c', + # For testing. Eventually, should use public API and be separate: + 'src/umath/_scaled_float_dtype.c', +] + +# SVML object files. If functionality is migrated to universal intrinsics and +# the object files are no longer needed, comment out the relevant object files +# here. Note that this migration is desirable; we then get the performance +# benefits for all platforms rather than only for AVX512 on 64-bit Linux, and +# may be able to avoid the accuracy regressions in SVML. +svml_objects = [] +if use_svml + svml_objects += [ + 'src/umath/svml/linux/avx512/svml_z0_acos_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_acos_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_acosh_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_acosh_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_asin_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_asin_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_asinh_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_asinh_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_atan2_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_atan2_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_atan_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_atan_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_atanh_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_atanh_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_cbrt_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_cbrt_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_cos_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_cos_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_cosh_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_cosh_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_exp2_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_exp2_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_exp_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_exp_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_expm1_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_expm1_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log10_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log10_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log1p_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log1p_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log2_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log2_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_log_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_pow_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_pow_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_sin_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_sin_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_sinh_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_sinh_s_la.s', + 'src/umath/svml/linux/avx512/svml_z0_tan_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_tan_s_la.s', + # 'src/umath/svml/linux/avx512/svml_z0_tanh_d_la.s', + 'src/umath/svml/linux/avx512/svml_z0_tanh_s_la.s', + ] +endif + +py.extension_module('_multiarray_umath', + [ + config_h, + _numpyconfig_h, + src_multiarray, + src_multiarray_umath_common, + src_umath, + src_ufunc_api[1], # __ufunc_api.h + src_numpy_api[1], # __multiarray_api.h + src_umath_doc_h, + npy_math_internal_h, + ], + objects: svml_objects, + c_args: c_args_common, + cpp_args: cpp_args_common, + include_directories: [ + 'include', + 'src/common', + 'src/multiarray', + 'src/npymath', + 'src/umath', + ], + dependencies: blas, + link_with: npymath_lib, + install: true, + subdir: 'numpy/core', +) + +# Build SIMD module +# ----------------- + +py.extension_module('_simd', + [ + 'src/common/npy_cpu_features.c', + 'src/_simd/_simd.c', + src_file.process('src/_simd/_simd_inc.h.src'), + src_file.process('src/_simd/_simd_data.inc.src'), + src_file.process('src/_simd/_simd.dispatch.c.src'), + ], + c_args: c_args_common, + #include_directories: ['src/multiarray', 'src/npymath'], + include_directories: ['src/_simd'], + dependencies: np_core_dep, + install: true, + subdir: 'numpy/core', +) + +python_sources = [ + '__init__.py', + '__init__.pyi', + '_add_newdocs.py', + '_add_newdocs_scalars.py', + '_asarray.py', + '_asarray.pyi', + '_dtype.py', + '_dtype_ctypes.py', + '_exceptions.py', + '_internal.py', + '_internal.pyi', + '_machar.py', + '_methods.py', + '_string_helpers.py', + '_type_aliases.py', + '_type_aliases.pyi', + '_ufunc_config.py', + '_ufunc_config.pyi', + 'arrayprint.py', + 'arrayprint.pyi', + 'cversions.py', + 'defchararray.py', + 'defchararray.pyi', + 'einsumfunc.py', + 'einsumfunc.pyi', + 'fromnumeric.py', + 'fromnumeric.pyi', + 'function_base.py', + 'function_base.pyi', + 'getlimits.py', + 'getlimits.pyi', + 'memmap.py', + 'memmap.pyi', + 'multiarray.py', + 'multiarray.pyi', + 'numeric.py', + 'numeric.pyi', + 'numerictypes.py', + 'numerictypes.pyi', + 'overrides.py', + 'records.py', + 'records.pyi', + 'shape_base.py', + 'shape_base.pyi', + 'umath.py', + 'umath_tests.py', +] + +py.install_sources( + python_sources, + subdir: 'numpy/core' +) + +subdir('include') +install_subdir('tests', install_dir: np_dir / 'core') diff --git a/numpy/core/setup.py b/numpy/core/setup.py index 10b8c093e..6f0a4417a 100644 --- a/numpy/core/setup.py +++ b/numpy/core/setup.py @@ -45,6 +45,8 @@ NPY_DISABLE_SVML = (os.environ.get('NPY_DISABLE_SVML', "0") == "1") # in time is only in build. -- Charles Harris, 2013-03-30 class CallOnceOnly: + # NOTE: we don't need any of this in the Meson build, + # it takes care of caching def __init__(self): self._check_types = None self._check_ieee_macros = None @@ -177,6 +179,8 @@ def check_math_capabilities(config, ext, moredefs, mathlibs): else: return 1 + # NOTE: not needed in Meson build, we set the minimum + # compiler version to 8.4 to avoid this bug # GH-14787: Work around GCC<8.4 bug when compiling with AVX512 # support on Windows-based platforms def check_gh14787(fn): @@ -427,6 +431,8 @@ def check_types(config_cmd, ext, build_dir): return private_defines, public_defines +# NOTE: this isn't needed in the Meson build, +# and we won't support a MATHLIB env var def check_mathlib(config_cmd): # Testing the C math library mathlibs = [] diff --git a/numpy/fft/meson.build b/numpy/fft/meson.build new file mode 100644 index 000000000..fedbf6349 --- /dev/null +++ b/numpy/fft/meson.build @@ -0,0 +1,33 @@ +largefile_define = [] +if host_machine.system() == 'aix' or host_machine.system() == 'AIX' + largefile_define += '-D_LARGE_FILES' +endif + +py.extension_module('_pocketfft_internal', + '_pocketfft.c', + c_args: largefile_define, + dependencies: np_core_dep, + install: true, + subdir: 'numpy/fft', +) + +py.install_sources( + [ + '__init__.py', + '__init__.pyi', + '_pocketfft.py', + '_pocketfft.pyi', + 'helper.py', + 'helper.pyi', + ], + subdir: 'numpy/fft' +) + +py.install_sources( + [ + 'tests/__init__.py', + 'tests/test_helper.py', + 'tests/test_pocketfft.py', + ], + subdir: 'numpy/fft/tests' +) diff --git a/numpy/linalg/meson.build b/numpy/linalg/meson.build new file mode 100644 index 000000000..083692913 --- /dev/null +++ b/numpy/linalg/meson.build @@ -0,0 +1,56 @@ +lapack_lite_sources = [ + 'lapack_lite/f2c.c', + 'lapack_lite/f2c_c_lapack.c', + 'lapack_lite/f2c_d_lapack.c', + 'lapack_lite/f2c_s_lapack.c', + 'lapack_lite/f2c_z_lapack.c', + 'lapack_lite/f2c_blas.c', + 'lapack_lite/f2c_config.c', + 'lapack_lite/f2c_lapack.c', + 'lapack_lite/python_xerbla.c', +] + +# TODO: ILP64 support + +lapack_lite_module_src = ['lapack_litemodule.c'] +if not have_lapack + warning('LAPACK was not found, NumPy is using an unoptimized, naive build from sources!') + lapack_lite_module_src += lapack_lite_sources +endif + +py.extension_module('lapack_lite', + lapack_lite_module_src, + dependencies: [np_core_dep, lapack], + install: true, + subdir: 'numpy/linalg', +) + +_umath_linalg_src = ['umath_linalg.cpp'] + lapack_lite_sources + +py.extension_module('_umath_linalg', + _umath_linalg_src, + dependencies: np_core_dep, + link_with: npymath_lib, + install: true, + subdir: 'numpy/linalg', +) + +py.install_sources( + [ + '__init__.py', + '__init__.pyi', + 'linalg.py', + 'linalg.pyi', + ], + subdir: 'numpy/linalg' +) + +py.install_sources( + [ + 'tests/__init__.py', + 'tests/test_deprecations.py', + 'tests/test_linalg.py', + 'tests/test_regression.py', + ], + subdir: 'numpy/linalg/tests' +) diff --git a/numpy/meson.build b/numpy/meson.build new file mode 100644 index 000000000..8b20769d0 --- /dev/null +++ b/numpy/meson.build @@ -0,0 +1,154 @@ +# We need -lm for all C code (assuming it uses math functions, which is safe to +# assume for numpy). +m_dep = cc.find_library('m', required : false) +mlib_linkflag = '' +if m_dep.found() + mlib_linkflag = '-lm' + add_project_link_arguments(mlib_linkflag, language : 'c') +endif + +# Platform detection +is_windows = host_machine.system() == 'windows' +is_mingw = is_windows and cc.get_id() == 'gcc' + +if is_windows + # For mingw-w64, link statically against the UCRT. + gcc_link_args = ['-lucrt', '-static'] + if is_mingw + add_project_link_arguments(gcc_link_args, language: ['c', 'cpp']) + # Force gcc to float64 long doubles for compatibility with MSVC + # builds, for C only. + add_project_arguments('-mlong-double-64', language: 'c') + # Make fprintf("%zd") work (see https://github.com/rgommers/scipy/issues/118) + add_project_arguments('-D__USE_MINGW_ANSI_STDIO=1', language: ['c', 'cpp']) + # Manual add of MS_WIN64 macro when not using MSVC. + # https://bugs.python.org/issue28267 + bitness = run_command('_build_utils/gcc_build_bitness.py').stdout().strip() + if bitness == '64' + add_project_arguments('-DMS_WIN64', language: ['c', 'cpp']) + endif + endif +endif + +# Enable UNIX large file support on 32-bit systems (64 bit off_t, +# lseek -> lseek64, etc.) +cflags_large_file_support = [] +if host_machine.system() == 'aix' + cflags_large_file_support += '-D_LARGE_FILES' +else + cflags_large_file_support += [ + '-D_FILE_OFFSET_BITS=64', + '-D_LARGEFILE_SOURCE=1', + '-D_LARGEFILE64_SOURCE=1', + ] +endif + + +# TODO: 64-bit BLAS and LAPACK +# +# Note that this works as long as BLAS and LAPACK are detected properly via +# pkg-config. By default we look for OpenBLAS, other libraries can be configured via +# `meson configure -Dblas=blas -Dlapack=lapack` (example to build with Netlib +# BLAS and LAPACK). +# For MKL and for auto-detecting one of multiple libs, we'll need a custom +# dependency in Meson (like is done for scalapack) - see +# https://github.com/mesonbuild/meson/issues/2835 +blas_name = get_option('blas') +lapack_name = get_option('lapack') +# pkg-config uses a lower-case name while CMake uses a capitalized name, so try +# that too to make the fallback detection with CMake work +if blas_name == 'openblas' + blas_name = ['openblas', 'OpenBLAS'] +endif +if lapack_name == 'openblas' + lapack_name = ['openblas', 'OpenBLAS'] +endif +blas = dependency(blas_name, required: false) +lapack = dependency(lapack_name, required: false) + +# BLAS and LAPACK are optional dependencies for NumPy. We can only use a BLAS +# which provides a CBLAS interface. +# TODO: add ILP64 support +have_blas = blas.found() # TODO: and blas.has_cblas() +have_lapack = lapack.found() + + +# TODO: generate __config__.py (see scipy/meson.build) + +# Copy the main __init__.py|pxd files to the build dir (needed for Cython) +__init__py = fs.copyfile('__init__.py') +__init__pxd = fs.copyfile('__init__.pxd') +__init__pxd30 = fs.copyfile('__init__.cython-30.pxd') +_cython_tree = [__init__py, __init__pxd, __init__pxd30] + +python_sources = [ + '__init__.cython-30.pxd', + '__init__.pxd', + '__init__.py', + '__init__.pyi', + '_distributor_init.py', + '_globals.py', + '_pytesttester.py', + '_pytesttester.pyi', + '_version.py', + 'conftest.py', + 'ctypeslib.py', + 'ctypeslib.pyi', + 'dual.py', + 'matlib.py', + 'py.typed', + 'version.py' +] + +py.install_sources( + python_sources, + subdir: 'numpy' +) + +src_file_cli = find_program('_build_utils/process_src_template.py') +src_file = generator(src_file_cli, + arguments : ['@INPUT@', '--outfile', '@OUTPUT@'], + output : '@BASENAME@' +) + +tempita_cli = find_program('_build_utils/tempita.py') +tempita = generator(tempita_cli, + arguments : ['@INPUT@', '--outfile', '@OUTPUT@'], + output : '@BASENAME@' +) + +pure_subdirs = [ + '_pyinstaller', + '_typing', + 'array_api', + 'compat', + 'distutils', + 'doc', + 'f2py', + 'lib', + 'ma', + 'matrixlib', + 'polynomial', + 'testing', + 'tests', + 'typing', +] + +np_dir = py.get_install_dir() / 'numpy' + +foreach subdir: pure_subdirs + install_subdir(subdir, install_dir: np_dir) +endforeach + +custom_target('__config__.py', + output: '__config__.py', + input: '__config__.py.in', + command: [tempita_cli, '@INPUT@', '-o', '@OUTPUT@'], + install: true, + install_dir: np_dir +) + +subdir('core') +subdir('fft') +subdir('linalg') +subdir('random') diff --git a/numpy/random/meson.build b/numpy/random/meson.build new file mode 100644 index 000000000..cc61c66dd --- /dev/null +++ b/numpy/random/meson.build @@ -0,0 +1,164 @@ +# Build npyrandom library +# ----------------------- +npyrandom_sources = [ + 'src/distributions/logfactorial.c', + 'src/distributions/distributions.c', + 'src/distributions/random_mvhg_count.c', + 'src/distributions/random_mvhg_marginals.c', + 'src/distributions/random_hypergeometric.c', +] + +npyrandom_lib = static_library('npyrandom', + npyrandom_sources, + c_args: staticlib_cflags, + # include_directories: '../core/include', + dependencies: [py_dep, np_core_dep], + install: true, + install_dir: np_dir / 'random/lib', +) + +# Build Cython extensions for numpy.random +# ---------------------------------------- +# pyx -> c transpile output depends on copied __init__.py and pxd files +_cython_tree_random = [ + fs.copyfile('__init__.py'), + fs.copyfile('__init__.pxd'), + fs.copyfile('_common.pxd'), + fs.copyfile('bit_generator.pxd'), + fs.copyfile('c_distributions.pxd'), +] +# Need to use `custom_target` because we need to install this .pxd file +_cython_tree_random += custom_target('_bounded_integer_pxd', + output: '_bounded_integers.pxd', + input: '_bounded_integers.pxd.in', + command: [tempita_cli, '@INPUT@', '-o', '@OUTPUT@'], + install: true, + install_dir: np_dir / 'random' +) + +_bounded_integers_pyx = custom_target('_bounded_integer_pyx', + output: '_bounded_integers.pyx', + input: '_bounded_integers.pyx.in', + command: [tempita_cli, '@INPUT@', '-o', '@OUTPUT@'], +) + +c_args_random = [ + cflags_large_file_support, + '-DNPY_NO_DEPRECATED_API=0', # Cython still uses old NumPy C API +] +if host_machine.system() == 'cygwin' + c_args_random += ['-Wl,--export-all-symbols'] +endif + +# name, sources, extra link libs, extra c_args +random_pyx_sources = [ + ['_bounded_integers', _bounded_integers_pyx, [], npymath_lib], + ['_common', '_common.pyx', [], []], + ['_mt19937', ['_mt19937.pyx', 'src/mt19937/mt19937.c', 'src/mt19937/mt19937-jump.c'], [], []], + ['_philox', ['_philox.pyx', 'src/philox/philox.c'], [], []], + ['_pcg64', ['_pcg64.pyx', 'src/pcg64/pcg64.c'], ['-U__GNUC_GNU_INLINE__'], []], + ['_sfc64', ['_sfc64.pyx', 'src/sfc64/sfc64.c'], [], []], + ['bit_generator', 'bit_generator.pyx', [], []], + # The `fs.copyfile` usage here is needed because these two .pyx files import + # from _bounded_integers,and its pxd file is only present in the build directory + ['_generator', fs.copyfile('_generator.pyx'), [], npymath_lib], + ['mtrand', [ + fs.copyfile('mtrand.pyx'), + 'src/distributions/distributions.c', + 'src/legacy/legacy-distributions.c' + ], ['-DNPY_RANDOM_LEGACY=1'], npymath_lib, + ], +] +foreach gen: random_pyx_sources + py.extension_module(gen[0], + [gen[1], _cython_tree, _cython_tree_random], + c_args: [c_args_random, gen[2]], + include_directories: 'src', + dependencies: np_core_dep, + link_with: [npyrandom_lib, gen[3]], + install: true, + subdir: 'numpy/random', + ) +endforeach + +# Install Python sources, stub files, tests, examples and license +# --------------------------------------------------------------- +py.install_sources( + [ + '__init__.pxd', + '__init__.py', + '__init__.pyi', + '_common.pxd', + '_generator.pyi', + '_mt19937.pyi', + '_pcg64.pyi', + '_pickle.py', + '_philox.pyi', + '_sfc64.pyi', + 'bit_generator.pxd', + 'bit_generator.pyi', + 'c_distributions.pxd', + 'LICENSE.md', + 'mtrand.pyi', + ], + subdir: 'numpy/random' +) + +py.install_sources( + [ + 'tests/__init__.py', + 'tests/test_direct.py', + 'tests/test_extending.py', + 'tests/test_generator_mt19937.py', + 'tests/test_generator_mt19937_regressions.py', + 'tests/test_random.py', + 'tests/test_randomstate.py', + 'tests/test_randomstate_regression.py', + 'tests/test_regression.py', + 'tests/test_seed_sequence.py', + 'tests/test_smoke.py', + ], + subdir: 'numpy/random/tests' +) + +py.install_sources( + [ + 'tests/data/__init__.py', + 'tests/data/mt19937-testset-1.csv', + 'tests/data/mt19937-testset-2.csv', + 'tests/data/pcg64-testset-1.csv', + 'tests/data/pcg64-testset-2.csv', + 'tests/data/pcg64dxsm-testset-1.csv', + 'tests/data/pcg64dxsm-testset-2.csv', + 'tests/data/philox-testset-1.csv', + 'tests/data/philox-testset-2.csv', + 'tests/data/sfc64-testset-1.csv', + 'tests/data/sfc64-testset-2.csv', + ], + subdir: 'numpy/random/tests/data' +) + +py.install_sources( + [ + '_examples/cffi/extending.py', + '_examples/cffi/parse.py', + ], + subdir: 'numpy/random/_examples/cffi' +) + +py.install_sources( + [ + '_examples/cython/extending.pyx', + '_examples/cython/extending_distributions.pyx', + '_examples/cython/setup.py', + ], + subdir: 'numpy/random/_examples/cython' +) + +py.install_sources( + [ + '_examples/numba/extending.py', + '_examples/numba/extending_distributions.py', + ], + subdir: 'numpy/random/_examples/numba' +) diff --git a/numpy/tests/test_public_api.py b/numpy/tests/test_public_api.py index 0c652e01f..35eb4fd00 100644 --- a/numpy/tests/test_public_api.py +++ b/numpy/tests/test_public_api.py @@ -320,6 +320,7 @@ SKIP_LIST = [ "numpy.core.code_generators.generate_ufunc_api", "numpy.core.code_generators.numpy_api", "numpy.core.code_generators.generate_umath_doc", + "numpy.core.code_generators.verify_c_api_version", "numpy.core.cversions", "numpy.core.generate_numpy_api", "numpy.distutils.msvc9compiler", diff --git a/numpy/version.py b/numpy/version.py index d5657d0d0..d9d2fe1b7 100644 --- a/numpy/version.py +++ b/numpy/version.py @@ -4,12 +4,20 @@ from ._version import get_versions __ALL__ = ['version', '__version__', 'full_version', 'git_revision', 'release'] + +_built_with_meson = False +try: + from ._version_meson import get_versions + _built_with_meson = True +except ImportError: + from ._version import get_versions + vinfo: dict[str, str] = get_versions() version = vinfo["version"] __version__ = vinfo.get("closest-tag", vinfo["version"]) -full_version = vinfo['version'] git_revision = vinfo['full-revisionid'] release = 'dev0' not in version and '+' not in version -short_version = vinfo['version'].split("+")[0] +full_version = version +short_version = version.split("+")[0] del get_versions, vinfo diff --git a/pyproject.toml b/pyproject.toml index 60e7f5826..b02250052 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,64 @@ [build-system] -# Minimum requirements for the build system to execute. +# Uncomment this line in order to build with Meson by default +#build-backend = "mesonpy" requires = [ + # setuptools, wheel and Cython are needed for the setup.py based build "setuptools==59.2.0", + # `wheel` is needed for non-isolated builds, given that `meson-python` + # doesn't list it as a runtime requirement (at least in 0.11.0) - it's + # likely to be removed as a dependency in meson-python 0.12.0. "wheel==0.37.0", "Cython>=0.29.30,<3.0", + "meson-python>=0.10.0", ] +[project] +name = "numpy" + +# Using https://peps.python.org/pep-0639/ +# which is still in draft +license = {text = "BSD-3-Clause"} +license-files.paths = [ + "LICENSE.txt", + "LICENSES_bundles.txt" +] + +description = "Fundamental package for array computing in Python" +maintainers = [ + {name = "NumPy Developers", email="numpy-discussion@python.org"}, +] +requires-python = ">=3.8" +readme = "README.md" +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.11', + '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', +] +dynamic = ["version"] + +[project.urls] +homepage = "https://numpy.org" +documentation = "https://numpy.org/doc/" +source = "https://github.com/numpy/numpy" +download = "https://pypi.org/project/numpy/#files" +tracker = "https://github.com/numpy/numpy/issues" [tool.towncrier] # Do no set this since it is hard to import numpy inside the source directory @@ -104,3 +157,10 @@ environment = { OPENBLAS64_="openblas", OPENBLAS="", NPY_USE_BLAS_ILP64="1", CFL [[tool.cibuildwheel.overrides]] select = "*-win32" environment = { OPENBLAS64_="", OPENBLAS="openblas", NPY_USE_BLAS_ILP64="0", CFLAGS="-m32", LDFLAGS="-m32" } + +[tool.devpy] +package = 'numpy' + +[tool.devpy.commands] +"Build" = ["devpy.build", "devpy.test"] +"Environments" = ["devpy.shell", "devpy.ipython", "devpy.python"] diff --git a/tools/check_installed_files.py b/tools/check_installed_files.py new file mode 100644 index 000000000..f15ca7969 --- /dev/null +++ b/tools/check_installed_files.py @@ -0,0 +1,86 @@ +""" +Check if all the test and .pyi files are installed after building. + +Examples:: + + $ python check_installed_files.py install_dirname + + install_dirname: + the relative path to the directory where NumPy is installed after + building and running `meson install`. + +Notes +===== + +The script will stop on encountering the first missing file in the install dir, +it will not give a full listing. This should be okay, because the script is +meant for use in CI so it's not like many files will be missing at once. + +""" + +import os +import glob +import sys + + +CUR_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__))) +ROOT_DIR = os.path.dirname(CUR_DIR) +NUMPY_DIR = os.path.join(ROOT_DIR, 'numpy') + + +# Files whose installation path will be different from original one +changed_installed_path = { + #'numpy/_build_utils/some_file.py': 'numpy/lib/some_file.py' +} + + +def main(install_dir): + INSTALLED_DIR = os.path.join(ROOT_DIR, install_dir) + if not os.path.exists(INSTALLED_DIR): + raise ValueError( + f"Provided install dir {INSTALLED_DIR} does not exist" + ) + + numpy_test_files = get_files(NUMPY_DIR, kind='test') + installed_test_files = get_files(INSTALLED_DIR, kind='test') + + # Check test files detected in repo are installed + for test_file in numpy_test_files.keys(): + if test_file not in installed_test_files.keys(): + raise Exception( + "%s is not installed" % numpy_test_files[test_file] + ) + + print("----------- All the test files were installed --------------") + + numpy_pyi_files = get_files(NUMPY_DIR, kind='stub') + installed_pyi_files = get_files(INSTALLED_DIR, kind='stub') + + # Check *.pyi files detected in repo are installed + for pyi_file in numpy_pyi_files.keys(): + if pyi_file not in installed_pyi_files.keys(): + raise Exception("%s is not installed" % numpy_pyi_files[pyi_file]) + + print("----------- All the .pyi files were installed --------------") + + +def get_files(dir_to_check, kind='test'): + files = dict() + patterns = { + 'test': f'{dir_to_check}/**/test_*.py', + 'stub': f'{dir_to_check}/**/*.pyi', + } + for path in glob.glob(patterns[kind], recursive=True): + relpath = os.path.relpath(path, dir_to_check) + files[relpath] = path + + return files + + +if __name__ == '__main__': + if not len(sys.argv) == 2: + raise ValueError("Incorrect number of input arguments, need " + "check_installation.py relpath/to/installed/numpy") + + install_dir = sys.argv[1] + main(install_dir) |
