diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ad79743953f..f5a08e2d787 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -135,7 +135,7 @@ The valid options are: * Use `-G` and the name of a generator to use something different. `cmake --help` lists the generators available. - On Unix, setting `CMAKE_GENERATER=Ninja` in your environment will give - you automatic mulithreading on all your CMake projects! + you automatic multithreading on all your CMake projects! * Open the `CMakeLists.txt` with QtCreator to generate for that IDE. * You can use `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to generate the `.json` file that some tools expect. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4f392c8c73..f9be10badad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,13 +32,13 @@ repos: # Black, the code formatter, natively supports pre-commit - repo: https://github.com/psf/black-pre-commit-mirror - rev: "23.7.0" # Keep in sync with blacken-docs + rev: "23.9.1" # Keep in sync with blacken-docs hooks: - id: black # Ruff, the Python auto-correcting linter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.287 + rev: v0.0.292 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -126,7 +126,7 @@ repos: # Use tools/codespell_ignore_lines_from_errors.py # to rebuild .codespell-ignore-lines - repo: https://github.com/codespell-project/codespell - rev: "v2.2.5" + rev: "v2.2.6" hooks: - id: codespell exclude: ".supp$" @@ -134,7 +134,7 @@ repos: # Check for common shell mistakes - repo: https://github.com/shellcheck-py/shellcheck-py - rev: "v0.9.0.5" + rev: "v0.9.0.6" hooks: - id: shellcheck @@ -144,12 +144,12 @@ repos: - id: disallow-caps name: Disallow improper capitalization language: pygrep - entry: PyBind|Numpy|Cmake|CCache|PyTest + entry: PyBind|\bNumpy\b|Cmake|CCache|PyTest exclude: ^\.pre-commit-config.yaml$ # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.0.0a7" + rev: "v3.0.0" hooks: - id: pylint files: ^pybind11 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b349508cc6..6321d6b144f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,11 @@ # All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. +# Propagate this policy (FindPythonInterp removal) so it can be detected later +if(NOT CMAKE_VERSION VERSION_LESS "3.27") + cmake_policy(GET CMP0148 _pybind11_cmp0148) +endif() + cmake_minimum_required(VERSION 3.5) # The `cmake_minimum_required(VERSION 3.5...3.26)` syntax does not work with @@ -16,6 +21,11 @@ else() cmake_policy(VERSION 3.26) endif() +if(_pybind11_cmp0148) + cmake_policy(SET CMP0148 ${_pybind11_cmp0148}) + unset(_pybind11_cmp0148) +endif() + # Avoid infinite recursion if tests include this as a subdirectory if(DEFINED PYBIND11_MASTER_PROJECT) return() diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index f6ab98e49e5..bfc2680225c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -817,7 +817,7 @@ class tuple_caster { } static constexpr auto name - = const_name("Tuple[") + concat(make_caster::name...) + const_name("]"); + = const_name("tuple[") + concat(make_caster::name...) + const_name("]"); template using cast_op_type = type; @@ -1044,6 +1044,10 @@ struct handle_type_name { static constexpr auto name = const_name(PYBIND11_BYTES_NAME); }; template <> +struct handle_type_name { + static constexpr auto name = const_name("Buffer"); +}; +template <> struct handle_type_name { static constexpr auto name = const_name("int"); }; @@ -1064,10 +1068,18 @@ struct handle_type_name { static constexpr auto name = const_name("Callable"); }; template <> +struct handle_type_name { + static constexpr auto name = handle_type_name::name; +}; +template <> struct handle_type_name { static constexpr auto name = const_name("None"); }; template <> +struct handle_type_name { + static constexpr auto name = const_name("Sequence"); +}; +template <> struct handle_type_name { static constexpr auto name = const_name("*args"); }; diff --git a/include/pybind11/detail/cross_extension_shared_state.h b/include/pybind11/detail/cross_extension_shared_state.h index 3d424cc44d4..ed36c8304ee 100644 --- a/include/pybind11/detail/cross_extension_shared_state.h +++ b/include/pybind11/detail/cross_extension_shared_state.h @@ -36,6 +36,7 @@ inline object get_python_state_dict() { #endif if (!state_dict) { raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED"); + throw error_already_set(); } return state_dict; } @@ -95,6 +96,7 @@ struct cross_extension_shared_state { " Retrieve payload_type** from capsule FAILED for ABI ID \"" + std::string(AdapterType::abi_id()) + "\"") .c_str()); + throw error_already_set(); } payload_pp() = static_cast(raw_ptr); return *payload_pp(); diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index c0ad7d05c8c..0b601d6d301 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -69,7 +69,7 @@ constexpr bool is_alias(void *) { } // Constructs and returns a new object; if the given arguments don't map to a constructor, we fall -// back to brace aggregate initiailization so that for aggregate initialization can be used with +// back to brace aggregate initialization so that for aggregate initialization can be used with // py::init, e.g. `py::init` to initialize a `struct T { int a; int b; }`. For // non-aggregate types, we need to use an ordinary T(...) constructor (invoking as `T{...}` usually // works, but will not do the expected thing when `T` has an `initializer_list` constructor). diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 9be2878aae8..ab2d3f1306e 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -324,6 +324,7 @@ inline internals **get_internals_pp_from_capsule(handle obj) { void *raw_ptr = PyCapsule_GetPointer(obj.ptr(), /*name=*/nullptr); if (raw_ptr == nullptr) { raise_from(PyExc_SystemError, "pybind11::detail::get_internals_pp_from_capsule() FAILED"); + throw error_already_set(); } return static_cast(raw_ptr); } diff --git a/include/pybind11/detail/native_enum_data.h b/include/pybind11/detail/native_enum_data.h index 09f783f4a80..11e7f792b87 100644 --- a/include/pybind11/detail/native_enum_data.h +++ b/include/pybind11/detail/native_enum_data.h @@ -98,6 +98,7 @@ inline void native_enum_add_to_parent(object parent, const detail::native_enum_d if (!enum_module) { raise_from(PyExc_SystemError, "`import enum` FAILED at " __FILE__ ":" PYBIND11_TOSTRING(__LINE__)); + throw error_already_set(); } auto py_enum_type = enum_module.attr(data.use_int_enum ? "IntEnum" : "Enum"); auto py_enum = py_enum_type(data.enum_name, data.members); diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 02f65d740dc..23c38660e9d 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -120,6 +120,20 @@ inline numpy_internals &get_numpy_internals() { return *ptr; } +PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name) { + module_ numpy = module_::import("numpy"); + str version_string = numpy.attr("__version__"); + + module_ numpy_lib = module_::import("numpy.lib"); + object numpy_version = numpy_lib.attr("NumpyVersion")(version_string); + int major_version = numpy_version.attr("major").cast(); + + /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially + became a private module. */ + std::string numpy_core_path = major_version >= 2 ? "numpy._core" : "numpy.core"; + return module_::import((numpy_core_path + "." + submodule_name).c_str()); +} + template struct same_size { template @@ -263,9 +277,13 @@ struct npy_api { }; static npy_api lookup() { - module_ m = module_::import("numpy.core.multiarray"); + module_ m = detail::import_numpy_core_submodule("multiarray"); auto c = m.attr("_ARRAY_API"); void **api_ptr = (void **) PyCapsule_GetPointer(c.ptr(), nullptr); + if (api_ptr == nullptr) { + raise_from(PyExc_SystemError, "FAILURE obtaining numpy _ARRAY_API pointer."); + throw error_already_set(); + } npy_api api; #define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func]; DECL_NPY_API(PyArray_GetNDArrayCFeatureVersion); @@ -626,11 +644,8 @@ class dtype : public object { private: static object _dtype_from_pep3118() { - static PyObject *obj = module_::import("numpy.core._internal") - .attr("_dtype_from_pep3118") - .cast() - .release() - .ptr(); + module_ m = detail::import_numpy_core_submodule("_internal"); + static PyObject *obj = m.attr("_dtype_from_pep3118").cast().release().ptr(); return reinterpret_borrow(obj); } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 68d3d36e6da..0e2108b8116 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -59,11 +59,13 @@ inline std::string replace_newlines_and_squash(const char *text) { std::string result(text); bool previous_is_whitespace = false; - // Do not modify string representations - char first_char = result[0]; - char last_char = result[result.size() - 1]; - if (first_char == last_char && first_char == '\'') { - return result; + if (result.size() >= 2) { + // Do not modify string representations + char first_char = result[0]; + char last_char = result[result.size() - 1]; + if (first_char == last_char && first_char == '\'') { + return result; + } } result.clear(); @@ -1146,7 +1148,7 @@ class cpp_function : public function { } msg += "kwargs: "; bool first = true; - for (auto kwarg : kwargs) { + for (const auto &kwarg : kwargs) { if (first) { first = false; } else { diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 804d9617c7f..0a5aa9cf25a 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -50,7 +50,7 @@ while pybind11 was very far on the strict side. Originally PyCLIF accepted any Python iterable as input for a C++ `vector`/`set`/`map` argument, as long as the elements were convertible. The obvious (in hindsight) problem was that any empty Python iterable could be passed to any of these C++ types, e.g. `{}` -was accpeted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments. +was accepted for C++ `vector`/`set` arguments, or `[]` for C++ `map` arguments. The functions below strike a practical permissive-vs-strict compromise, informed by tens of thousands of use cases in the wild. A main objective is @@ -202,7 +202,7 @@ struct set_caster { return s.release(); } - PYBIND11_TYPE_CASTER_RVPP(type, const_name("Set[") + key_conv::name + const_name("]")); + PYBIND11_TYPE_CASTER_RVPP(type, const_name("set[") + key_conv::name + const_name("]")); }; template @@ -273,7 +273,7 @@ struct map_caster { } PYBIND11_TYPE_CASTER_RVPP(Type, - const_name("Dict[") + key_conv::name + const_name(", ") + const_name("dict[") + key_conv::name + const_name(", ") + value_conv::name + const_name("]")); }; @@ -340,7 +340,7 @@ struct list_caster { return l.release(); } - PYBIND11_TYPE_CASTER_RVPP(Type, const_name("List[") + value_conv::name + const_name("]")); + PYBIND11_TYPE_CASTER_RVPP(Type, const_name("list[") + value_conv::name + const_name("]")); }; template @@ -422,7 +422,7 @@ struct array_caster { PYBIND11_TYPE_CASTER_RVPP(ArrayType, const_name(const_name(""), const_name("Annotated[")) - + const_name("List[") + value_conv::name + const_name("]") + + const_name("list[") + value_conv::name + const_name("]") + const_name(const_name(""), const_name(", FixedSize(") + const_name() diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 74fd82eacee..b7b1a4e54aa 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -45,6 +45,16 @@ class Set : public set { using set::set; }; +template +class Iterable : public iterable { + using iterable::iterable; +}; + +template +class Iterator : public iterator { + using iterator::iterator; +}; + template class Callable; @@ -85,6 +95,16 @@ struct handle_type_name> { static constexpr auto name = const_name("set[") + make_caster::name + const_name("]"); }; +template +struct handle_type_name> { + static constexpr auto name = const_name("Iterable[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("Iterator[") + make_caster::name + const_name("]"); +}; + template struct handle_type_name> { using retval_type = conditional_t::value, void_type, Return>; diff --git a/tests/test_buffers.py b/tests/test_buffers.py index 63d9d869fda..5d33625ba47 100644 --- a/tests/test_buffers.py +++ b/tests/test_buffers.py @@ -219,3 +219,10 @@ def test_ctypes_from_buffer(): assert cinfo.shape == pyinfo.shape assert cinfo.strides == pyinfo.strides assert not cinfo.readonly + + +def test_buffer_docstring(): + assert ( + m.get_buffer_info.__doc__.strip() + == "get_buffer_info(arg0: Buffer) -> pybind11_tests.buffers.buffer_info" + ) diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index 61daad468ba..0fb5ef61e99 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -352,7 +352,7 @@ def test_tuple(doc): assert ( doc(m.pair_passthrough) == """ - pair_passthrough(arg0: Tuple[bool, str]) -> Tuple[str, bool] + pair_passthrough(arg0: tuple[bool, str]) -> tuple[str, bool] Return a pair in reversed order """ @@ -360,7 +360,7 @@ def test_tuple(doc): assert ( doc(m.tuple_passthrough) == """ - tuple_passthrough(arg0: Tuple[bool, str, int]) -> Tuple[int, str, bool] + tuple_passthrough(arg0: tuple[bool, str, int]) -> tuple[int, str, bool] Return a triple in reversed order """ diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py index f59d6cf2417..958e13e4a5f 100644 --- a/tests/test_factory_constructors.py +++ b/tests/test_factory_constructors.py @@ -77,7 +77,7 @@ def test_init_factory_signature(msg): 1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) 2. m.factory_constructors.TestFactory1(arg0: str) 3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag) - 4. m.factory_constructors.TestFactory1(arg0: handle, arg1: int, arg2: handle) + 4. m.factory_constructors.TestFactory1(arg0: object, arg1: int, arg2: object) Invoked with: 'invalid', 'constructor', 'arguments' """ @@ -95,7 +95,7 @@ def test_init_factory_signature(msg): 3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None - 4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None + 4. __init__(self: m.factory_constructors.TestFactory1, arg0: object, arg1: int, arg2: object) -> None """ ) diff --git a/tests/test_kwargs_and_defaults.cpp b/tests/test_kwargs_and_defaults.cpp index 614c826dac3..9a12c42afa6 100644 --- a/tests/test_kwargs_and_defaults.cpp +++ b/tests/test_kwargs_and_defaults.cpp @@ -85,6 +85,8 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { "kw_lb_func7", [](const std::string &) {}, py::arg("str_arg") = "First line.\n Second line."); + m.def( + "kw_lb_func8", [](const CustomRepr &) {}, py::arg("custom") = CustomRepr("")); // test_args_and_kwargs m.def("args_function", [](py::args args) -> py::tuple { diff --git a/tests/test_kwargs_and_defaults.py b/tests/test_kwargs_and_defaults.py index b577001976a..0e3bbdb7652 100644 --- a/tests/test_kwargs_and_defaults.py +++ b/tests/test_kwargs_and_defaults.py @@ -8,7 +8,7 @@ def test_function_signatures(doc): assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str" assert doc(m.kw_func2) == "kw_func2(x: int = 100, y: int = 200) -> str" assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None" - assert doc(m.kw_func4) == "kw_func4(myList: List[int] = [13, 17]) -> str" + assert doc(m.kw_func4) == "kw_func4(myList: list[int] = [13, 17]) -> str" assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int = 300) -> str" assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int = 0) -> str" assert doc(m.args_function) == "args_function(*args) -> tuple" @@ -55,6 +55,10 @@ def test_function_signatures(doc): doc(m.kw_lb_func7) == "kw_lb_func7(str_arg: str = 'First line.\\n Second line.') -> None" ) + assert ( + doc(m.kw_lb_func8) + == "kw_lb_func8(custom: m.kwargs_and_defaults.CustomRepr = ) -> None" + ) def test_named_arguments(): diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 12e7d17d152..0697daf3efc 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -298,7 +298,7 @@ def test_constructors(): results = m.converting_constructors([1, 2, 3]) for a in results.values(): np.testing.assert_array_equal(a, [1, 2, 3]) - assert results["array"].dtype == np.int_ + assert results["array"].dtype == np.dtype(int) assert results["array_t"].dtype == np.int32 assert results["array_t"].dtype == np.float64 diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index b03f5624e15..0a587680f85 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -828,6 +828,8 @@ TEST_SUBMODULE(pytypes, m) { m.def("annotate_dict_str_int", [](const py::typing::Dict &) {}); m.def("annotate_list_int", [](const py::typing::List &) {}); m.def("annotate_set_str", [](const py::typing::Set &) {}); + m.def("annotate_iterable_str", [](const py::typing::Iterable &) {}); + m.def("annotate_iterator_int", [](const py::typing::Iterator &) {}); m.def("annotate_fn", [](const py::typing::Callable, py::str)> &) {}); } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index e88a9328f7a..2b2027316ca 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -926,6 +926,20 @@ def test_set_annotations(doc): assert doc(m.annotate_set_str) == "annotate_set_str(arg0: set[str]) -> None" +def test_iterable_annotations(doc): + assert ( + doc(m.annotate_iterable_str) + == "annotate_iterable_str(arg0: Iterable[str]) -> None" + ) + + +def test_iterator_annotations(doc): + assert ( + doc(m.annotate_iterator_int) + == "annotate_iterator_int(arg0: Iterator[int]) -> None" + ) + + def test_fn_annotations(doc): assert ( doc(m.annotate_fn) diff --git a/tests/test_sequences_and_iterators.py b/tests/test_sequences_and_iterators.py index dc129f2bff4..acbe9d8983e 100644 --- a/tests/test_sequences_and_iterators.py +++ b/tests/test_sequences_and_iterators.py @@ -171,6 +171,10 @@ def __len__(self): assert m.sequence_length("hello") == 5 +def test_sequence_doc(): + assert m.sequence_length.__doc__.strip() == "sequence_length(arg0: Sequence) -> int" + + def test_map_iterator(): sm = m.StringMap({"hi": "bye", "black": "white"}) assert sm["hi"] == "bye" diff --git a/tests/test_stl.py b/tests/test_stl.py index 2a7e916c4d0..328cc85dc31 100644 --- a/tests/test_stl.py +++ b/tests/test_stl.py @@ -16,8 +16,8 @@ def test_vector(doc): assert m.load_bool_vector([True, False]) assert m.load_bool_vector((True, False)) - assert doc(m.cast_vector) == "cast_vector() -> List[int]" - assert doc(m.load_vector) == "load_vector(arg0: List[int]) -> bool" + assert doc(m.cast_vector) == "cast_vector() -> list[int]" + assert doc(m.load_vector) == "load_vector(arg0: list[int]) -> bool" # Test regression caused by 936: pointers to stl containers weren't castable assert m.cast_ptr_vector() == ["lvalue", "lvalue"] @@ -39,10 +39,10 @@ def test_array(doc): assert m.load_array(lst) assert m.load_array(tuple(lst)) - assert doc(m.cast_array) == "cast_array() -> Annotated[List[int], FixedSize(2)]" + assert doc(m.cast_array) == "cast_array() -> Annotated[list[int], FixedSize(2)]" assert ( doc(m.load_array) - == "load_array(arg0: Annotated[List[int], FixedSize(2)]) -> bool" + == "load_array(arg0: Annotated[list[int], FixedSize(2)]) -> bool" ) @@ -53,8 +53,8 @@ def test_valarray(doc): assert m.load_valarray(lst) assert m.load_valarray(tuple(lst)) - assert doc(m.cast_valarray) == "cast_valarray() -> List[int]" - assert doc(m.load_valarray) == "load_valarray(arg0: List[int]) -> bool" + assert doc(m.cast_valarray) == "cast_valarray() -> list[int]" + assert doc(m.load_valarray) == "load_valarray(arg0: list[int]) -> bool" def test_map(doc): @@ -66,8 +66,8 @@ def test_map(doc): assert "key2" in d assert m.load_map(d) - assert doc(m.cast_map) == "cast_map() -> Dict[str, str]" - assert doc(m.load_map) == "load_map(arg0: Dict[str, str]) -> bool" + assert doc(m.cast_map) == "cast_map() -> dict[str, str]" + assert doc(m.load_map) == "load_map(arg0: dict[str, str]) -> bool" def test_set(doc): @@ -78,8 +78,8 @@ def test_set(doc): assert m.load_set(s) assert m.load_set(frozenset(s)) - assert doc(m.cast_set) == "cast_set() -> Set[str]" - assert doc(m.load_set) == "load_set(arg0: Set[str]) -> bool" + assert doc(m.cast_set) == "cast_set() -> set[str]" + assert doc(m.load_set) == "load_set(arg0: set[str]) -> bool" def test_recursive_casting(): @@ -303,7 +303,7 @@ def test_stl_pass_by_pointer(msg): msg(excinfo.value) == """ stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: - 1. (v: List[int] = None) -> List[int] + 1. (v: list[int] = None) -> list[int] Invoked with: """ @@ -315,7 +315,7 @@ def test_stl_pass_by_pointer(msg): msg(excinfo.value) == """ stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: - 1. (v: List[int] = None) -> List[int] + 1. (v: list[int] = None) -> list[int] Invoked with: None """ diff --git a/tools/FindPythonLibsNew.cmake b/tools/FindPythonLibsNew.cmake index ce558d4ece6..8275b9d5aaf 100644 --- a/tools/FindPythonLibsNew.cmake +++ b/tools/FindPythonLibsNew.cmake @@ -95,6 +95,22 @@ if(NOT PythonLibsNew_FIND_VERSION) set(PythonLibsNew_FIND_VERSION "3.6") endif() +if(NOT CMAKE_VERSION VERSION_LESS "3.27") + cmake_policy(GET CMP0148 _pybind11_cmp0148) + if(NOT _pybind11_cmp0148) + message( + AUTHOR_WARNING + "Policy CMP0148 is not set: The FindPythonInterp and FindPythonLibs " + "modules are removed. Run \"cmake --help-policy CMP0148\" for policy " + "details. Use the cmake_policy command to set the policy and suppress " + "this warning, or preferably upgrade to using FindPython, either by " + "calling it explicitly before pybind11, or by setting " + "PYBIND11_FINDPYTHON ON before pybind11.") + endif() + cmake_policy(SET CMP0148 OLD) + unset(_pybind11_cmp0148) +endif() + find_package(PythonInterp ${PythonLibsNew_FIND_VERSION} ${_pythonlibs_required} ${_pythonlibs_quiet}) @@ -172,13 +188,20 @@ _pybind11_get_if_undef(_PYTHON_VALUES 0 _PYTHON_VERSION_LIST) _pybind11_get_if_undef(_PYTHON_VALUES 1 PYTHON_PREFIX) _pybind11_get_if_undef(_PYTHON_VALUES 2 PYTHON_INCLUDE_DIR) _pybind11_get_if_undef(_PYTHON_VALUES 3 PYTHON_SITE_PACKAGES) -_pybind11_get_if_undef(_PYTHON_VALUES 4 PYTHON_MODULE_EXTENSION) _pybind11_get_if_undef(_PYTHON_VALUES 5 PYTHON_IS_DEBUG) _pybind11_get_if_undef(_PYTHON_VALUES 6 PYTHON_SIZEOF_VOID_P) _pybind11_get_if_undef(_PYTHON_VALUES 7 PYTHON_LIBRARY_SUFFIX) _pybind11_get_if_undef(_PYTHON_VALUES 8 PYTHON_LIBDIR) _pybind11_get_if_undef(_PYTHON_VALUES 9 PYTHON_MULTIARCH) +list(GET _PYTHON_VALUES 4 _PYTHON_MODULE_EXT_SUFFIX) +if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX) + get_filename_component(PYTHON_MODULE_DEBUG_POSTFIX "${_PYTHON_MODULE_EXT_SUFFIX}" NAME_WE) +endif() +if(PYBIND11_PYTHONLIBS_OVERWRITE OR NOT DEFINED PYTHON_MODULE_EXTENSION) + get_filename_component(PYTHON_MODULE_EXTENSION "${_PYTHON_MODULE_EXT_SUFFIX}" EXT) +endif() + # Make sure the Python has the same pointer-size as the chosen compiler # Skip if CMAKE_SIZEOF_VOID_P is not defined # This should be skipped for (non-Apple) cross-compiles (like EMSCRIPTEN) diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index 3a35a738631..8cee2d46091 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -95,25 +95,36 @@ endif() # Get the suffix - SO is deprecated, should use EXT_SUFFIX, but this is # required for PyPy3 (as of 7.3.1) -if(NOT DEFINED PYTHON_MODULE_EXTENSION) +if(NOT DEFINED PYTHON_MODULE_EXTENSION OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX) execute_process( COMMAND "${${_Python}_EXECUTABLE}" "-c" "import sys, importlib; s = importlib.import_module('distutils.sysconfig' if sys.version_info < (3, 10) else 'sysconfig'); print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO'))" - OUTPUT_VARIABLE _PYTHON_MODULE_EXTENSION - ERROR_VARIABLE _PYTHON_MODULE_EXTENSION_ERR + OUTPUT_VARIABLE _PYTHON_MODULE_EXT_SUFFIX + ERROR_VARIABLE _PYTHON_MODULE_EXT_SUFFIX_ERR OUTPUT_STRIP_TRAILING_WHITESPACE) - if(_PYTHON_MODULE_EXTENSION STREQUAL "") + if(_PYTHON_MODULE_EXT_SUFFIX STREQUAL "") message( FATAL_ERROR "pybind11 could not query the module file extension, likely the 'distutils'" - "package is not installed. Full error message:\n${_PYTHON_MODULE_EXTENSION_ERR}") + "package is not installed. Full error message:\n${_PYTHON_MODULE_EXT_SUFFIX_ERR}" + ) endif() # This needs to be available for the pybind11_extension function - set(PYTHON_MODULE_EXTENSION - "${_PYTHON_MODULE_EXTENSION}" - CACHE INTERNAL "") + if(NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX) + get_filename_component(_PYTHON_MODULE_DEBUG_POSTFIX "${_PYTHON_MODULE_EXT_SUFFIX}" NAME_WE) + set(PYTHON_MODULE_DEBUG_POSTFIX + "${_PYTHON_MODULE_DEBUG_POSTFIX}" + CACHE INTERNAL "") + endif() + + if(NOT DEFINED PYTHON_MODULE_EXTENSION) + get_filename_component(_PYTHON_MODULE_EXTENSION "${_PYTHON_MODULE_EXT_SUFFIX}" EXT) + set(PYTHON_MODULE_EXTENSION + "${_PYTHON_MODULE_EXTENSION}" + CACHE INTERNAL "") + endif() endif() # Python debug libraries expose slightly different objects before 3.8 @@ -253,6 +264,9 @@ endfunction() function(pybind11_extension name) # The extension is precomputed - set_target_properties(${name} PROPERTIES PREFIX "" SUFFIX "${PYTHON_MODULE_EXTENSION}") - + set_target_properties( + ${name} + PROPERTIES PREFIX "" + DEBUG_POSTFIX "${PYTHON_MODULE_DEBUG_POSTFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}") endfunction() diff --git a/tools/pybind11Tools.cmake b/tools/pybind11Tools.cmake index 67e710bda5e..1b1774d09ef 100644 --- a/tools/pybind11Tools.cmake +++ b/tools/pybind11Tools.cmake @@ -65,6 +65,7 @@ _pybind11_promote_to_cache(PYTHON_INCLUDE_DIRS) _pybind11_promote_to_cache(PYTHON_LIBRARIES) _pybind11_promote_to_cache(PYTHON_MODULE_PREFIX) _pybind11_promote_to_cache(PYTHON_MODULE_EXTENSION) +_pybind11_promote_to_cache(PYTHON_MODULE_DEBUG_POSTFIX) _pybind11_promote_to_cache(PYTHON_VERSION_MAJOR) _pybind11_promote_to_cache(PYTHON_VERSION_MINOR) _pybind11_promote_to_cache(PYTHON_VERSION) @@ -148,8 +149,11 @@ endif() function(pybind11_extension name) # The prefix and extension are provided by FindPythonLibsNew.cmake - set_target_properties(${name} PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" - SUFFIX "${PYTHON_MODULE_EXTENSION}") + set_target_properties( + ${name} + PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + DEBUG_POSTFIX "${PYTHON_MODULE_DEBUG_POSTFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}") endfunction() # Build a Python extension module: