diff options
author | Ganesh Kathiresan <ganesh3597@gmail.com> | 2023-01-24 15:49:43 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-24 11:19:43 +0100 |
commit | 04823c1a95e23c55432a78c7c7414c040279c2e1 (patch) | |
tree | f5a885b5fe175131cefe770213c9d9eab95f054c | |
parent | 172a1942893b5ce55abccd836fdd9f00235a6767 (diff) | |
download | numpy-04823c1a95e23c55432a78c7c7414c040279c2e1.tar.gz |
BLD: Meson `__config__` generation (#22769)
Add functionality to autogenerate build information for a Meson-based build.
In order to add new information, do the following:
- Add the information as an argument in `numpy/meson.build`
- Modify `__config__.py.in` to accept the new argument
Note that SIMD information is added to config, but is WIP/empty,
because `__cpu*` lists are not yet populated as meson does not build
SIMD features yet.
There are two display modes:
- `stdout`: Uses `PyYaml` to display in a human friendly
format. Uses `json` if `PyYaml` is not installed
- `dicts`: Returns a `dict` object
Things will work fine without `pyyaml` installed, an unobtrusive
warning is displayed that the printed output will look better
with `pyyaml`.
[ci skip]
-rw-r--r-- | doc/release/upcoming_changes/22769.improvement.rst | 5 | ||||
-rw-r--r-- | numpy/__config__.py.in | 270 | ||||
-rw-r--r-- | numpy/meson.build | 66 | ||||
-rw-r--r-- | numpy/tests/test_numpy_config.py | 44 |
4 files changed, 256 insertions, 129 deletions
diff --git a/doc/release/upcoming_changes/22769.improvement.rst b/doc/release/upcoming_changes/22769.improvement.rst new file mode 100644 index 000000000..3566648ac --- /dev/null +++ b/doc/release/upcoming_changes/22769.improvement.rst @@ -0,0 +1,5 @@ +`np.show_config` uses information from Meson +-------------------------------------------- +Build and system information now contains information from Meson. +`np.show_config` now has a new optional parameter ``mode`` to help +customize the output. diff --git a/numpy/__config__.py.in b/numpy/__config__.py.in index cda615904..659a09b26 100644 --- a/numpy/__config__.py.in +++ b/numpy/__config__.py.in @@ -1,52 +1,136 @@ # 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(): +from enum import Enum +from numpy.core._multiarray_umath import ( + __cpu_features__, + __cpu_baseline__, + __cpu_dispatch__, +) + +__all__ = ["show"] +_built_with_meson = True + + +class DisplayModes(Enum): + stdout = "stdout" + dicts = "dicts" + + +def _cleanup(d): """ - Show libraries in the system on which NumPy was built. + Removes empty values in a `dict` recursively + This ensures we remove values that Meson could not provide to CONFIG + """ + if type(d) is dict: + return dict( + (k, _cleanup(v)) for k, v in d.items() if v and _cleanup(v) + ) + else: + return d + + +CONFIG = _cleanup( + { + "Compilers": { + "c": { + "name": "@C_COMP@", + "linker": "@C_COMP_LINKER_ID@", + "version": "@C_COMP_VERSION@", + "commands": "@C_COMP_CMD_ARRAY@", + }, + "cython": { + "name": "@CYTHON_COMP@", + "linker": "@CYTHON_COMP_LINKER_ID@", + "version": "@CYTHON_COMP_VERSION@", + "commands": "@CYTHON_COMP_CMD_ARRAY@", + }, + "c++": { + "name": "@CPP_COMP@", + "linker": "@CPP_COMP_LINKER_ID@", + "version": "@CPP_COMP_VERSION@", + "commands": "@CPP_COMP_CMD_ARRAY@", + }, + }, + "Machine Information": { + "host": { + "cpu": "@HOST_CPU@", + "family": "@HOST_CPU_FAMILY@", + "endian": "@HOST_CPU_ENDIAN@", + "system": "@HOST_CPU_SYSTEM@", + }, + "build": { + "cpu": "@BUILD_CPU@", + "family": "@BUILD_CPU_FAMILY@", + "endian": "@BUILD_CPU_ENDIAN@", + "system": "@BUILD_CPU_SYSTEM@", + }, + "cross-compiled": "@CROSS_COMPILED@", + }, + "Build Dependencies": { + "blas": { + "name": "@BLAS_NAME@", + "found": "@BLAS_FOUND@", + "version": "@BLAS_VERSION@", + "detection method": "@BLAS_TYPE_NAME@", + "include directory": "@BLAS_INCLUDEDIR@", + "lib directory": "@BLAS_LIBDIR@", + "openblas configuration": "@BLAS_OPENBLAS_CONFIG@", + "pc file directory": "@BLAS_PCFILEDIR@", + }, + "lapack": { + "name": "@LAPACK_NAME@", + "found": "@LAPACK_FOUND@", + "version": "@LAPACK_VERSION@", + "detection method": "@LAPACK_TYPE_NAME@", + "include directory": "@LAPACK_INCLUDEDIR@", + "lib directory": "@LAPACK_LIBDIR@", + "openblas configuration": "@LAPACK_OPENBLAS_CONFIG@", + "pc file directory": "@LAPACK_PCFILEDIR@", + }, + }, + "Python Information": { + "path": "@PYTHON_PATH@", + "version": "@PYTHON_VERSION@", + }, + "SIMD Extensions": { + "baseline": __cpu_baseline__, + "found": [ + feature + for feature in __cpu_dispatch__ + if __cpu_features__[feature] + ], + "not found": [ + feature + for feature in __cpu_dispatch__ + if not __cpu_features__[feature] + ], + }, + } +) + + +def _check_pyyaml(): + import yaml + + return yaml + + +def show(mode=DisplayModes.stdout.value): + """ + Show libraries and system information on which NumPy was built + and is being used + + Parameters + ---------- + mode : {`'stdout'`, `'dicts'`}, optional. + Indicates how to display the config information. + `'stdout'` prints to console, `'dicts'` returns a dictionary + of the configuration. - Print information about various resources (libraries, library - directories, include directories, etc.) in the system on which - NumPy was built. + Returns + ------- + out : {`dict`, `None`} + If mode is `'dicts'`, a dict is returned, else None See Also -------- @@ -55,80 +139,24 @@ def show(): 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'] + 1. The `'stdout'` mode will give more readable + output if ``pyyaml`` is installed + """ - 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))) + if mode == DisplayModes.stdout.value: + try: # Non-standard library, check import + yaml = _check_pyyaml() + + print(yaml.dump(CONFIG)) + except ModuleNotFoundError: + import warnings + import json + + warnings.warn("Install `pyyaml` for better output", stacklevel=1) + print(json.dumps(CONFIG, indent=2)) + elif mode == DisplayModes.dicts.value: + return CONFIG + else: + raise AttributeError( + f"Invalid `mode`, use one of: {', '.join([e.value for e in DisplayModes])}" + ) diff --git a/numpy/meson.build b/numpy/meson.build index 23bd83a04..ec131ebb5 100644 --- a/numpy/meson.build +++ b/numpy/meson.build @@ -66,15 +66,17 @@ endif blas = dependency(blas_name, required: false) lapack = dependency(lapack_name, required: false) +dependency_map = { + 'BLAS': blas, + 'LAPACK': lapack, +} + # 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') @@ -143,12 +145,60 @@ foreach subdir: pure_subdirs install_subdir(subdir, install_dir: np_dir) endforeach -custom_target('__config__.py', - output: '__config__.py', +compilers = { + 'C': cc, + 'CPP': cpp, + 'CYTHON': meson.get_compiler('cython') +} + +machines = { + 'HOST': host_machine, + 'BUILD': build_machine, +} + +conf_data = configuration_data() + +# Set compiler information +foreach name, compiler : compilers + conf_data.set(name + '_COMP', compiler.get_id()) + conf_data.set(name + '_COMP_LINKER_ID', compiler.get_linker_id()) + conf_data.set(name + '_COMP_VERSION', compiler.version()) + conf_data.set(name + '_COMP_CMD_ARRAY', compiler.cmd_array()) +endforeach + +# Machines CPU and system information +foreach name, machine : machines + conf_data.set(name + '_CPU', machine.cpu()) + conf_data.set(name + '_CPU_FAMILY', machine.cpu_family()) + conf_data.set(name + '_CPU_ENDIAN', machine.endian()) + conf_data.set(name + '_CPU_SYSTEM', machine.system()) +endforeach + +conf_data.set('CROSS_COMPILED', meson.is_cross_build()) + +# Python information +conf_data.set('PYTHON_PATH', py.full_path()) +conf_data.set('PYTHON_VERSION', py.language_version()) + +# Dependencies information +foreach name, dep : dependency_map + conf_data.set(name + '_NAME', dep.name()) + conf_data.set(name + '_FOUND', dep.found()) + if dep.found() + conf_data.set(name + '_VERSION', dep.version()) + conf_data.set(name + '_TYPE_NAME', dep.type_name()) + conf_data.set(name + '_INCLUDEDIR', dep.get_variable('includedir')) + conf_data.set(name + '_LIBDIR', dep.get_variable('libdir')) + conf_data.set(name + '_OPENBLAS_CONFIG', dep.get_variable('openblas_config')) + conf_data.set(name + '_PCFILEDIR', dep.get_variable('pcfiledir')) + endif +endforeach + +configure_file( input: '__config__.py.in', - command: [tempita_cli, '@INPUT@', '-o', '@OUTPUT@'], - install: true, - install_dir: np_dir + output: '__config__.py', + configuration : conf_data, + install_dir: np_dir, ) subdir('core') diff --git a/numpy/tests/test_numpy_config.py b/numpy/tests/test_numpy_config.py new file mode 100644 index 000000000..82c1ad70b --- /dev/null +++ b/numpy/tests/test_numpy_config.py @@ -0,0 +1,44 @@ +""" +Check the numpy config is valid. +""" +import numpy as np +import pytest +from unittest.mock import Mock, patch + +pytestmark = pytest.mark.skipif( + not hasattr(np.__config__, "_built_with_meson"), + reason="Requires Meson builds", +) + + +class TestNumPyConfigs: + REQUIRED_CONFIG_KEYS = [ + "Compilers", + "Machine Information", + "Python Information", + ] + + @patch("numpy.__config__._check_pyyaml") + def test_pyyaml_not_found(self, mock_yaml_importer): + mock_yaml_importer.side_effect = ModuleNotFoundError() + with pytest.warns(UserWarning): + np.show_config() + + def test_dict_mode(self): + config = np.show_config(mode="dicts") + + assert isinstance(config, dict) + assert all([key in config for key in self.REQUIRED_CONFIG_KEYS]), ( + "Required key missing," + " see index of `False` with `REQUIRED_CONFIG_KEYS`" + ) + + def test_invalid_mode(self): + with pytest.raises(AttributeError): + np.show_config(mode="foo") + + def test_warn_to_add_tests(self): + assert len(np.__config__.DisplayModes) == 2, ( + "New mode detected," + " please add UT if applicable and increment this count" + ) |