summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
authorSebastian Berg <sebastianb@nvidia.com>2023-04-04 16:46:56 +0200
committerSebastian Berg <sebastianb@nvidia.com>2023-04-04 17:17:17 +0200
commit3a811358830c324b7b6819b88dec4e4bcd91444a (patch)
tree62a3387e47d173ee888b0017bec527d5307ea467 /numpy
parent6309cf291ca806f554723d659777acca46cdad1f (diff)
downloadnumpy-3a811358830c324b7b6819b88dec4e4bcd91444a.tar.gz
ENH: Allow compiling compatibly to old NumPy versions
The default compiles compatibly with 1.17.x, we allow going back to 1.15 (mainly because it is easy). There were few additions in this time, a few structs grew and very few API functions were added. Added a way to mark API functions as requiring a specific target version. If a user wishes to use the *new* API, they have to add the definition: #define NPY_TARGET_VERSION NPY_1_22_API_VERSION Before importing NumPy. (Our version numbering is a bit funny I first thought to use a hex version of the main NumPy version, but since we already have the `NPY_1_22_API_VERSION` defines...)
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/code_generators/cversions.txt4
-rw-r--r--numpy/core/code_generators/genapi.py58
-rw-r--r--numpy/core/code_generators/numpy_api.py12
-rw-r--r--numpy/core/include/numpy/arrayscalars.h4
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h16
-rw-r--r--numpy/core/include/numpy/ufuncobject.h6
-rw-r--r--numpy/core/setup_common.py7
7 files changed, 84 insertions, 23 deletions
diff --git a/numpy/core/code_generators/cversions.txt b/numpy/core/code_generators/cversions.txt
index bd4b71180..e52193d7a 100644
--- a/numpy/core/code_generators/cversions.txt
+++ b/numpy/core/code_generators/cversions.txt
@@ -67,5 +67,7 @@
# Version 16 (NumPy 1.23)
# NonNull attributes removed from numpy_api.py
# Version 16 (NumPy 1.24) No change.
-# Version 16 (NumPy 1.25) No change.
0x00000010 = 04a7bf1e65350926a0e528798da263c0
+
+# Version 17 (NumPy 1.25) No actual change.
+0x00000011 = ca1aebdad799358149567d9d93cbca09
diff --git a/numpy/core/code_generators/genapi.py b/numpy/core/code_generators/genapi.py
index f23b5a564..c5a16d2b8 100644
--- a/numpy/core/code_generators/genapi.py
+++ b/numpy/core/code_generators/genapi.py
@@ -12,6 +12,7 @@ import os
import re
import sys
import importlib.util
+import textwrap
from os.path import join
@@ -98,6 +99,33 @@ def _repl(str):
return str.replace('Bool', 'npy_bool')
+class MinVersion:
+ def __init__(self, version):
+ """ Version should be the normal NumPy version, e.g. "1.25" """
+ major, minor = version.split(".")
+ self.version = f"NPY_{major}_{minor}_API_VERSION"
+
+ def __str__(self):
+ # Used by version hashing:
+ return self.version
+
+ def add_guard(self, name, normal_define):
+ """Wrap a definition behind a version guard"""
+ numpy_target_help_pointer = (
+ "(Old_NumPy_target see also: "
+ "https://numpy.org/devdocs/dev/depending_on_numpy.html)")
+
+ wrap = textwrap.dedent(f"""
+ #if NPY_FEATURE_VERSION < {self.version}
+ #define {name} {numpy_target_help_pointer}
+ #else
+ {{define}}
+ #endif""")
+
+ # we only insert `define` later to avoid confusing dedent:
+ return wrap.format(define=normal_define)
+
+
class StealRef:
def __init__(self, arg):
self.arg = arg # counting from 1
@@ -391,7 +419,20 @@ class FunctionApi:
def __init__(self, name, index, annotations, return_type, args, api_name):
self.name = name
self.index = index
- self.annotations = annotations
+
+ self.min_version = None
+ self.annotations = []
+ for annotation in annotations:
+ # String checks, because manual import breaks isinstance
+ if type(annotation).__name__ == "StealRef":
+ self.annotations.append(annotation)
+ elif type(annotation).__name__ == "MinVersion":
+ if self.min_version is not None:
+ raise ValueError("Two minimum versions specified!")
+ self.min_version = annotation
+ else:
+ raise ValueError(f"unknown annotation {annotation}")
+
self.return_type = return_type
self.args = args
self.api_name = api_name
@@ -403,13 +444,14 @@ class FunctionApi:
return argstr
def define_from_array_api_string(self):
- define = """\
-#define %s \\\n (*(%s (*)(%s)) \\
- %s[%d])""" % (self.name,
- self.return_type,
- self._argtypes_string(),
- self.api_name,
- self.index)
+ arguments = self._argtypes_string()
+ define = textwrap.dedent(f"""\
+ #define {self.name} \\
+ (*({self.return_type} (*)({arguments})) \\
+ {self.api_name}[{self.index}])""")
+
+ if self.min_version is not None:
+ define = self.min_version.add_guard(self.name, define)
return define
def array_api_define(self):
diff --git a/numpy/core/code_generators/numpy_api.py b/numpy/core/code_generators/numpy_api.py
index 6b9080403..bb3b15806 100644
--- a/numpy/core/code_generators/numpy_api.py
+++ b/numpy/core/code_generators/numpy_api.py
@@ -18,17 +18,17 @@ import os
import importlib.util
-def get_StealRef():
+def get_annotations():
# 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
+ return mod.StealRef, mod.MinVersion
-StealRef = get_StealRef()
+StealRef, MinVersion = get_annotations()
#from code_generators.genapi import StealRef
# index, type
@@ -367,8 +367,8 @@ multiarray_funcs_api = {
'PyArray_ResolveWritebackIfCopy': (302,),
'PyArray_SetWritebackIfCopyBase': (303,),
# End 1.14 API
- 'PyDataMem_SetHandler': (304,),
- 'PyDataMem_GetHandler': (305,),
+ 'PyDataMem_SetHandler': (304, MinVersion("1.22")),
+ 'PyDataMem_GetHandler': (305, MinVersion("1.22")),
# End 1.22 API
}
@@ -422,7 +422,7 @@ ufunc_funcs_api = {
# End 1.7 API
'PyUFunc_RegisterLoopForDescr': (41,),
# End 1.8 API
- 'PyUFunc_FromFuncAndDataAndSignatureAndIdentity': (42,),
+ 'PyUFunc_FromFuncAndDataAndSignatureAndIdentity': (42, MinVersion("1.16")),
# End 1.16 API
}
diff --git a/numpy/core/include/numpy/arrayscalars.h b/numpy/core/include/numpy/arrayscalars.h
index a20a68016..258bf95b6 100644
--- a/numpy/core/include/numpy/arrayscalars.h
+++ b/numpy/core/include/numpy/arrayscalars.h
@@ -139,7 +139,9 @@ typedef struct {
/* note that the PyObject_HEAD macro lives right here */
PyUnicodeObject base;
Py_UCS4 *obval;
+ #if NPY_FEATURE_VERSION >= NPY_1_20_API_VERSION
char *buffer_fmt;
+ #endif
} PyUnicodeScalarObject;
@@ -149,7 +151,9 @@ typedef struct {
PyArray_Descr *descr;
int flags;
PyObject *base;
+ #if NPY_FEATURE_VERSION >= NPY_1_20_API_VERSION
void *_buffer_info; /* private buffer info, tagged to allow warning */
+ #endif
} PyVoidScalarObject;
/* Macros
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index f771d62cd..742ba5261 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -712,11 +712,15 @@ typedef struct tagPyArrayObject_fields {
int flags;
/* For weak references */
PyObject *weakreflist;
+#if NPY_FEATURE_VERSION >= NPY_1_20_API_VERSION
void *_buffer_info; /* private buffer info, tagged to allow warning */
+#endif
/*
* For malloc/calloc/realloc/free per object
*/
+#if NPY_FEATURE_VERSION >= NPY_1_22_API_VERSION
PyObject *mem_handler;
+#endif
} PyArrayObject_fields;
/*
@@ -1654,11 +1658,13 @@ PyArray_CLEARFLAGS(PyArrayObject *arr, int flags)
((PyArrayObject_fields *)arr)->flags &= ~flags;
}
-static inline NPY_RETURNS_BORROWED_REF PyObject *
-PyArray_HANDLER(PyArrayObject *arr)
-{
- return ((PyArrayObject_fields *)arr)->mem_handler;
-}
+#if NPY_FEATURE_VERSION >= NPY_1_22_API_VERSION
+ static inline NPY_RETURNS_BORROWED_REF PyObject *
+ PyArray_HANDLER(PyArrayObject *arr)
+ {
+ return ((PyArrayObject_fields *)arr)->mem_handler;
+ }
+#endif
#define PyTypeNum_ISBOOL(type) ((type) == NPY_BOOL)
diff --git a/numpy/core/include/numpy/ufuncobject.h b/numpy/core/include/numpy/ufuncobject.h
index bb0633100..9e00f2e56 100644
--- a/numpy/core/include/numpy/ufuncobject.h
+++ b/numpy/core/include/numpy/ufuncobject.h
@@ -197,7 +197,7 @@ typedef struct _tagPyUFuncObject {
npy_uint32 iter_flags;
/* New in NPY_API_VERSION 0x0000000D and above */
-
+ #if NPY_FEATURE_VERSION >= NPY_1_16_API_VERSION
/*
* for each core_num_dim_ix distinct dimension names,
* the possible "frozen" size (-1 if not frozen).
@@ -211,13 +211,15 @@ typedef struct _tagPyUFuncObject {
/* Identity for reduction, when identity == PyUFunc_IdentityValue */
PyObject *identity_value;
+ #endif /* NPY_FEATURE_VERSION >= NPY_1_16_API_VERSION */
/* New in NPY_API_VERSION 0x0000000F and above */
-
+ #if NPY_FEATURE_VERSION >= NPY_1_22_API_VERSION
/* New private fields related to dispatching */
void *_dispatch_cache;
/* A PyListObject of `(tuple of DTypes, ArrayMethod/Promoter)` */
PyObject *_loops;
+ #endif
} PyUFuncObject;
#include "arrayobject.h"
diff --git a/numpy/core/setup_common.py b/numpy/core/setup_common.py
index af8eeecde..cec934973 100644
--- a/numpy/core/setup_common.py
+++ b/numpy/core/setup_common.py
@@ -48,7 +48,8 @@ C_ABI_VERSION = 0x01000009
# 0x0000000f - 1.22.x
# 0x00000010 - 1.23.x
# 0x00000010 - 1.24.x
-C_API_VERSION = 0x00000010
+# 0x00000011 - 1.25.x
+C_API_VERSION = 0x00000011
# When compiling against NumPy (downstream libraries), NumPy will by default
# pick an older feature version. For example, for 1.25.x we default to the
@@ -96,6 +97,10 @@ def check_api_version(apiversion, codegen_dir):
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__}."
+ "\n"
+ "Please make sure that new additions are guarded with "
+ "MinVersion() to make them unavailable when wishing support "
+ "for older NumPy versions."
)
raise MismatchCAPIError(msg)