diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9500f38..182085558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog - Unreleased + - Adjust struct definitions to match the selected NumPy C API version (#534) + - Add features to select the NumPy C API version (#534) - Fix PyArray_DTypeMeta definition when Py_LIMITED_API is disabled (#532) - v0.28.0 diff --git a/Cargo.toml b/Cargo.toml index 111834ca9..c5470983d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ authors = [ ] description = "PyO3-based Rust bindings of the NumPy C-API" documentation = "https://docs.rs/numpy" -edition = "2021" -rust-version = "1.83" +edition.workspace = true +rust-version.workspace = true repository = "https://github.com/PyO3/rust-numpy" categories = ["api-bindings", "development-tools::ffi", "science"] keywords = ["python", "numpy", "ffi", "pyo3"] @@ -20,6 +20,29 @@ exclude = [ "x.py", ] +[features] +default = ["target-npy119"] + +# Default and minimum supported version are chosen to match the content of +# header `numpy/_core/include/numpy/numpyconfig.h`` in the first available +# version of numpy v2. +target-npy115 = ["numpy-build-config/target-npy115"] +target-npy116 = ["numpy-build-config/target-npy116"] +target-npy117 = ["numpy-build-config/target-npy117"] +target-npy118 = ["numpy-build-config/target-npy118"] +target-npy119 = ["numpy-build-config/target-npy119"] +target-npy120 = ["numpy-build-config/target-npy120"] +target-npy121 = ["numpy-build-config/target-npy121"] +target-npy122 = ["numpy-build-config/target-npy122"] +target-npy123 = ["numpy-build-config/target-npy123"] +target-npy124 = ["numpy-build-config/target-npy124"] +target-npy125 = ["numpy-build-config/target-npy125"] +target-npy20 = ["numpy-build-config/target-npy20"] +target-npy21 = ["numpy-build-config/target-npy21"] +target-npy22 = ["numpy-build-config/target-npy22"] +target-npy23 = ["numpy-build-config/target-npy23"] +target-npy24 = ["numpy-build-config/target-npy24"] + [dependencies] half = { version = "2.0", default-features = false, optional = true } libc = "0.2" @@ -37,6 +60,7 @@ nalgebra = { version = ">=0.30, <0.35", default-features = false, features = ["s [build-dependencies] pyo3-build-config = { version = "0.28", features = ["resolve-config"]} +numpy-build-config = { path = "numpy-build-config", version = "0.28.0" } [package.metadata.docs.rs] all-features = true @@ -47,3 +71,10 @@ elided-lifetimes-in-paths = "deny" [lints.clippy] needless-lifetimes = "allow" + +[workspace.package] +edition = "2021" +rust-version = "1.83" + +[workspace] +members = ["numpy-build-config"] diff --git a/build.rs b/build.rs index 0475124bb..2b1f14ad5 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,4 @@ fn main() { pyo3_build_config::use_pyo3_cfgs(); + numpy_build_config::use_numpy_cfgs(); } diff --git a/examples/linalg/Cargo.lock b/examples/linalg/Cargo.lock index 4b8c1a5bc..b7f47e80e 100644 --- a/examples/linalg/Cargo.lock +++ b/examples/linalg/Cargo.lock @@ -223,12 +223,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indoc" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" - [[package]] name = "lapack" version = "0.18.0" @@ -301,15 +295,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -398,18 +383,23 @@ dependencies = [ [[package]] name = "numpy" -version = "0.27.0" +version = "0.28.0" dependencies = [ "libc", "ndarray", "num-complex", "num-integer", "num-traits", + "numpy-build-config", "pyo3", "pyo3-build-config", "rustc-hash", ] +[[package]] +name = "numpy-build-config" +version = "0.28.0" + [[package]] name = "once_cell" version = "1.21.3" @@ -524,35 +514,32 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8e48c12afdeb26aa4be4e5c49fb5e11c3efa0878db783a960eea2b9ac6dd19" +checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1" dependencies = [ - "indoc", "libc", - "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", - "unindent", ] [[package]] name = "pyo3-build-config" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc1989dbf2b60852e0782c7487ebf0b4c7f43161ffe820849b56cf05f945cee1" +checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c808286da7500385148930152e54fb6883452033085bf1f857d85d4e82ca905c" +checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc" dependencies = [ "libc", "pyo3-build-config", @@ -560,9 +547,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0543c16be0d86cf0dbf2e2b636ece9fd38f20406bb43c255e0bc368095f92" +checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -572,9 +559,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.27.0" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a00da2ce064dcd582448ea24a5a26fa9527e0483103019b741ebcbe632dcd29" +checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a" dependencies = [ "heck", "proc-macro2", @@ -585,9 +572,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -796,9 +783,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" @@ -869,12 +856,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unindent" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" - [[package]] name = "ureq" version = "2.10.1" diff --git a/numpy-build-config/Cargo.toml b/numpy-build-config/Cargo.toml new file mode 100644 index 000000000..c6ae38b61 --- /dev/null +++ b/numpy-build-config/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "numpy-build-config" +version = "0.28.0" +authors = [ + "The rust-numpy Project Developers", + "PyO3 Project and Contributors ", +] +description = "Build configuration for the numpy crate" +edition = "2021" +rust-version = "1.83" +repository = "https://github.com/PyO3/rust-numpy" +license = "BSD-2-Clause" + +[dependencies] + +[features] +default = [] +target-npy115 = [] +target-npy116 = ["target-npy115"] +target-npy117 = ["target-npy116"] +target-npy118 = ["target-npy117"] +target-npy119 = ["target-npy118"] +target-npy120 = ["target-npy119"] +target-npy121 = ["target-npy120"] +target-npy122 = ["target-npy121"] +target-npy123 = ["target-npy122"] +target-npy124 = ["target-npy123"] +target-npy125 = ["target-npy124"] +target-npy20 = ["target-npy125"] +target-npy21 = ["target-npy20"] +target-npy22 = ["target-npy21"] +target-npy23 = ["target-npy22"] +target-npy24 = ["target-npy23"] diff --git a/numpy-build-config/LICENSE b/numpy-build-config/LICENSE new file mode 100644 index 000000000..d5b45151c --- /dev/null +++ b/numpy-build-config/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2017, Toshiki Teramura +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/numpy-build-config/src/impl_.rs b/numpy-build-config/src/impl_.rs new file mode 100644 index 000000000..d300b1a41 --- /dev/null +++ b/numpy-build-config/src/impl_.rs @@ -0,0 +1,101 @@ +#[derive(Debug, Clone, Copy)] +pub struct NumpyVersion { + pub minor: u32, + pub major: u32, +} + +#[allow(non_snake_case)] +impl NumpyVersion { + const fn V1(minor: u32) -> Self { + Self { major: 1, minor } + } + const fn V2(minor: u32) -> Self { + Self { major: 2, minor } + } +} + +impl NumpyVersion { + /// An iterator over supported versions of numpy API. + pub fn supported() -> impl Iterator { + SUPPORTED_VERSIONS.iter().copied() + } + + /// An iterator over enabled versions of numpy API. + pub fn enabled() -> impl Iterator { + ENABLED_VERSIONS.iter().copied() + } +} + +const SUPPORTED_VERSIONS: &[NumpyVersion] = &[ + NumpyVersion::V1(15), + NumpyVersion::V1(16), + NumpyVersion::V1(17), + NumpyVersion::V1(18), + NumpyVersion::V1(19), + NumpyVersion::V1(20), + NumpyVersion::V1(21), + NumpyVersion::V1(22), + NumpyVersion::V1(23), + NumpyVersion::V1(24), + NumpyVersion::V1(25), + NumpyVersion::V2(0), + NumpyVersion::V2(1), + NumpyVersion::V2(2), + NumpyVersion::V2(3), + NumpyVersion::V2(4), +]; + +const ENABLED_VERSIONS: &[NumpyVersion] = &[ + #[cfg(feature = "target-npy115")] + NumpyVersion::V1(15), // 0x0000000c + #[cfg(any( + feature = "target-npy116", + feature = "target-npy117", + feature = "target-npy118", + feature = "target-npy119" + ))] + NumpyVersion::V1(16), // 0x0000000d + #[cfg(any( + feature = "target-npy116", + feature = "target-npy117", + feature = "target-npy118", + feature = "target-npy119" + ))] + NumpyVersion::V1(17), // 0x0000000d + #[cfg(any( + feature = "target-npy116", + feature = "target-npy117", + feature = "target-npy118", + feature = "target-npy119" + ))] + NumpyVersion::V1(18), // 0x0000000d + #[cfg(any( + feature = "target-npy116", + feature = "target-npy117", + feature = "target-npy118", + feature = "target-npy119" + ))] + NumpyVersion::V1(19), // 0x0000000d + #[cfg(any(feature = "target-npy120", feature = "target-npy121"))] + NumpyVersion::V1(20), // 0x0000000e + #[cfg(any(feature = "target-npy120", feature = "target-npy121"))] + NumpyVersion::V1(21), // 0x0000000e + #[cfg(feature = "target-npy122")] + NumpyVersion::V1(22), // 0x0000000f + #[cfg(any(feature = "target-npy123", feature = "target-npy124"))] + NumpyVersion::V1(23), // 0x00000010 + #[cfg(any(feature = "target-npy123", feature = "target-npy124"))] + NumpyVersion::V1(24), // 0x00000010 + #[cfg(feature = "target-npy125")] + NumpyVersion::V1(25), // 0x00000011 + #[cfg(feature = "target-npy20")] + NumpyVersion::V2(0), // 0x00000012 + #[cfg(any(feature = "target-npy21", feature = "target-npy22"))] + NumpyVersion::V2(1), // 0x00000013 + #[cfg(any(feature = "target-npy21", feature = "target-npy22"))] + NumpyVersion::V2(2), // 0x00000013 + #[cfg(feature = "target-npy23")] + NumpyVersion::V2(3), // 0x00000014 + #[cfg(feature = "target-npy24")] + NumpyVersion::V2(4), // 0x00000015 +]; diff --git a/numpy-build-config/src/lib.rs b/numpy-build-config/src/lib.rs new file mode 100644 index 000000000..b9a9a2ff9 --- /dev/null +++ b/numpy-build-config/src/lib.rs @@ -0,0 +1,23 @@ +use self::impl_::NumpyVersion; + +mod impl_; + +pub fn use_numpy_cfgs() { + print_expected_features(); + print_enabled_features(); +} + +fn print_expected_features() { + for version in NumpyVersion::supported() { + println!( + "cargo:rustc-check-cfg=cfg(Numpy_{}_{})", + version.major, version.minor + ); + } +} + +fn print_enabled_features() { + for version in NumpyVersion::enabled() { + println!("cargo:rustc-cfg=Numpy_{}_{}", version.major, version.minor); + } +} diff --git a/src/npyffi/objects.rs b/src/npyffi/objects.rs index 4f4f1aaaf..75969fe4f 100644 --- a/src/npyffi/objects.rs +++ b/src/npyffi/objects.rs @@ -21,6 +21,12 @@ pub struct PyArrayObject { pub descr: *mut PyArray_Descr, pub flags: c_int, pub weakreflist: *mut PyObject, + + #[cfg(Numpy_1_20)] + pub _buffer_info: *mut c_void, + + #[cfg(Numpy_1_22)] + pub mem_handler: *mut PyObject, } #[repr(C)] @@ -32,6 +38,19 @@ pub struct PyArray_Descr { pub byteorder: c_char, pub _former_flags: c_char, pub type_num: c_int, + + #[cfg(Numpy_2_0)] + pub flags: npy_uint64, + #[cfg(Numpy_2_0)] + pub elsize: npy_intp, + #[cfg(Numpy_2_0)] + pub alignment: npy_intp, + #[cfg(Numpy_2_0)] + pub metadata: *mut PyObject, + #[cfg(Numpy_2_0)] + pub hash: npy_hash_t, + #[cfg(Numpy_2_0)] + pub reserved_null: [*mut c_void; 2], } #[repr(C)] @@ -365,11 +384,49 @@ pub struct PyUFuncObject { pub core_offsets: *mut c_int, pub core_signature: *mut c_char, pub type_resolver: PyUFunc_TypeResolutionFunc, + + #[cfg(not(Numpy_2_0))] pub legacy_inner_loop_selector: PyUFunc_LegacyInnerLoopSelectionFunc, + #[cfg(all(Numpy_2_0, not(Numpy_2_2)))] + pub reserved2: *mut c_void, + #[cfg(Numpy_2_2)] + pub dict: *mut PyObject, + + #[cfg(not(Numpy_1_21))] + pub reserved2: *mut c_void, + #[cfg(all(Numpy_1_21, not(Numpy_1_22), Py_3_8))] + pub vectorcall: Option, + #[cfg(all(Numpy_1_21, not(Numpy_1_22), not(Py_3_8)))] pub reserved2: *mut c_void, + #[cfg(all(Numpy_1_22, Py_3_8, not(Py_LIMITED_API)))] + pub vectorcall: Option, + #[cfg(all(Numpy_1_22, not(all(Py_3_8, not(Py_LIMITED_API)))))] + pub vectorcall: *mut c_void, + + #[cfg(not(Numpy_1_22))] pub masked_inner_loop_selector: PyUFunc_MaskedInnerLoopSelectionFunc, + #[cfg(all(Numpy_1_22, not(Numpy_2_0)))] + pub _always_null_previously_masked_innerloop_selector: *mut c_void, + #[cfg(Numpy_2_0)] + pub reserved3: *mut c_void, + pub op_flags: *mut npy_uint32, pub iter_flags: npy_uint32, + + #[cfg(Numpy_1_16)] + pub core_dim_sizes: *mut npy_intp, + #[cfg(Numpy_1_16)] + pub core_dim_flags: *mut npy_uint32, + #[cfg(Numpy_1_16)] + pub identity_value: *mut PyObject, + + #[cfg(Numpy_1_22)] + pub _dispatch_cache: *mut c_void, + #[cfg(Numpy_1_22)] + pub _loops: *mut PyObject, + + #[cfg(Numpy_2_1)] + pub process_core_dims_func: PyUFunc_ProcessCoreDimsFunc, } pub type PyUFuncGenericFunction = @@ -393,6 +450,8 @@ pub type PyUFunc_TypeResolutionFunc = Option< *mut *mut PyArray_Descr, ) -> c_int, >; + +#[cfg(not(Numpy_2_0))] pub type PyUFunc_LegacyInnerLoopSelectionFunc = Option< unsafe extern "C" fn( *mut PyUFuncObject, @@ -402,6 +461,8 @@ pub type PyUFunc_LegacyInnerLoopSelectionFunc = Option< *mut c_int, ) -> c_int, >; + +#[cfg(not(Numpy_1_22))] pub type PyUFunc_MaskedInnerLoopSelectionFunc = Option< unsafe extern "C" fn( *mut PyUFuncObject, @@ -415,6 +476,10 @@ pub type PyUFunc_MaskedInnerLoopSelectionFunc = Option< ) -> c_int, >; +#[cfg(Numpy_2_1)] +pub type PyUFunc_ProcessCoreDimsFunc = + Option c_int>; + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NpyIter([u8; 0]); diff --git a/src/npyffi/types.rs b/src/npyffi/types.rs index eb03b801b..e88a00552 100644 --- a/src/npyffi/types.rs +++ b/src/npyffi/types.rs @@ -154,6 +154,9 @@ pub enum NPY_SELECTKIND { NPY_INTROSELECT = 0, } +#[cfg(Numpy_2_4)] +pub const NPY_SAME_VALUE_CASTING_FLAG: u32 = 64; + #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_CASTING { @@ -162,6 +165,9 @@ pub enum NPY_CASTING { NPY_SAFE_CASTING = 2, NPY_SAME_KIND_CASTING = 3, NPY_UNSAFE_CASTING = 4, + + #[cfg(Numpy_2_4)] + NPY_SAME_VALUE_CASTING = Self::NPY_UNSAFE_CASTING as u32 | NPY_SAME_VALUE_CASTING_FLAG, } #[repr(u32)]