Merge branch 'master' into sh_merge_master

This commit is contained in:
Ralf W. Grosse-Kunstleve 2021-11-15 14:43:08 -08:00
commit f4fab85d87
15 changed files with 167 additions and 31 deletions

View File

@ -705,14 +705,11 @@ jobs:
- name: Install Doxygen - name: Install Doxygen
run: sudo apt-get install -y doxygen librsvg2-bin # Changed to rsvg-convert in 20.04 run: sudo apt-get install -y doxygen librsvg2-bin # Changed to rsvg-convert in 20.04
- name: Install docs & setup requirements
run: python3 -m pip install -r docs/requirements.txt
- name: Build docs - name: Build docs
run: python3 -m sphinx -W -b html docs docs/.build run: pipx run nox -s docs
- name: Make SDist - name: Make SDist
run: python3 setup.py sdist run: pipx run nox -s build -- --sdist
- run: git status --ignored - run: git status --ignored
@ -724,7 +721,7 @@ jobs:
- name: Compare Dists (headers only) - name: Compare Dists (headers only)
working-directory: include working-directory: include
run: | run: |
python3 -m pip install --user -U ../dist/* python3 -m pip install --user -U ../dist/*.tar.gz
installed=$(python3 -c "import pybind11; print(pybind11.get_include() + '/pybind11')") installed=$(python3 -c "import pybind11; print(pybind11.get_include() + '/pybind11')")
diff -rq $installed ./pybind11 diff -rq $installed ./pybind11

View File

@ -39,7 +39,7 @@ repos:
- id: pyupgrade - id: pyupgrade
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.9.3 rev: 5.10.0
hooks: hooks:
- id: isort - id: isort
@ -63,6 +63,12 @@ repos:
- id: remove-tabs - id: remove-tabs
exclude: (^docs/.*|\.patch)?$ exclude: (^docs/.*|\.patch)?$
# Autoremoves unused imports
- repo: https://github.com/hadialqattan/pycln
rev: v1.1.0
hooks:
- id: pycln
- repo: https://github.com/pre-commit/pygrep-hooks - repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.9.0 rev: v1.9.0
hooks: hooks:

View File

@ -18,6 +18,5 @@ ALIASES += "endrst=\endverbatim"
QUIET = YES QUIET = YES
WARNINGS = YES WARNINGS = YES
WARN_IF_UNDOCUMENTED = NO WARN_IF_UNDOCUMENTED = NO
PREDEFINED = DOXYGEN_SHOULD_SKIP_THIS \ PREDEFINED = PY_MAJOR_VERSION=3 \
PY_MAJOR_VERSION=3 \
PYBIND11_NOINLINE PYBIND11_NOINLINE

View File

@ -52,7 +52,7 @@ can be mapped *and* if the numpy array is writeable (that is
the passed variable will be transparently carried out directly on the the passed variable will be transparently carried out directly on the
``numpy.ndarray``. ``numpy.ndarray``.
This means you can can write code such as the following and have it work as This means you can write code such as the following and have it work as
expected: expected:
.. code-block:: cpp .. code-block:: cpp

View File

@ -1,8 +1,5 @@
breathe==4.26.1 breathe==4.31.0
# docutils 0.17 breaks HTML tags & RTD theme sphinx==3.5.4
# https://github.com/sphinx-doc/sphinx/issues/9001 sphinx_rtd_theme==1.0.0
docutils==0.16 sphinxcontrib-moderncmakedomain==3.19
sphinx==3.3.1 sphinxcontrib-svg2pdfconverter==1.1.1
sphinx_rtd_theme==0.5.0
sphinxcontrib-moderncmakedomain==3.17
sphinxcontrib-svg2pdfconverter==1.1.0

View File

@ -2093,6 +2093,16 @@ inline std::pair<decltype(internals::registered_types_py)::iterator, bool> all_t
// gets destroyed: // gets destroyed:
weakref((PyObject *) type, cpp_function([type](handle wr) { weakref((PyObject *) type, cpp_function([type](handle wr) {
get_internals().registered_types_py.erase(type); get_internals().registered_types_py.erase(type);
// TODO consolidate the erasure code in pybind11_meta_dealloc() in class.h
auto &cache = get_internals().inactive_override_cache;
for (auto it = cache.begin(), last = cache.end(); it != last; ) {
if (it->first == reinterpret_cast<PyObject *>(type))
it = cache.erase(it);
else
++it;
}
wr.dec_ref(); wr.dec_ref();
})).release(); })).release();
} }

View File

@ -287,10 +287,10 @@ protected:
struct borrowed_t { }; struct borrowed_t { };
struct stolen_t { }; struct stolen_t { };
#ifndef DOXYGEN_SHOULD_SKIP_THIS // Issue in breathe 4.26.1 /// @cond BROKEN
template <typename T> friend T reinterpret_borrow(handle); template <typename T> friend T reinterpret_borrow(handle);
template <typename T> friend T reinterpret_steal(handle); template <typename T> friend T reinterpret_steal(handle);
#endif /// @endcond
public: public:
// Only accessible from derived classes and the reinterpret_* functions // Only accessible from derived classes and the reinterpret_* functions
@ -1717,7 +1717,7 @@ public:
#endif #endif
}; };
#ifndef DOXYGEN_SHOULD_SKIP_THIS /// @cond DUPLICATE
inline memoryview memoryview::from_buffer( inline memoryview memoryview::from_buffer(
void *ptr, ssize_t itemsize, const char* format, void *ptr, ssize_t itemsize, const char* format,
detail::any_container<ssize_t> shape, detail::any_container<ssize_t> shape,
@ -1745,7 +1745,7 @@ inline memoryview memoryview::from_buffer(
throw error_already_set(); throw error_already_set();
return memoryview(object(obj, stolen_t{})); return memoryview(object(obj, stolen_t{}));
} }
#endif // DOXYGEN_SHOULD_SKIP_THIS /// @endcond
/// @} pytypes /// @} pytypes
/// \addtogroup python_builtins /// \addtogroup python_builtins

View File

@ -57,10 +57,10 @@ def docs(session: nox.Session) -> None:
session.chdir("docs") session.chdir("docs")
if "pdf" in session.posargs: if "pdf" in session.posargs:
session.run("sphinx-build", "-M", "latexpdf", ".", "_build") session.run("sphinx-build", "-b", "latexpdf", ".", "_build")
return return
session.run("sphinx-build", "-M", "html", ".", "_build") session.run("sphinx-build", "-b", "html", ".", "_build")
if "serve" in session.posargs: if "serve" in session.posargs:
session.log("Launching docs at http://localhost:8000/ - use Ctrl-C to quit") session.log("Launching docs at http://localhost:8000/ - use Ctrl-C to quit")
@ -86,6 +86,8 @@ def build(session: nox.Session) -> None:
session.install("build") session.install("build")
session.log("Building normal files") session.log("Building normal files")
session.run("python", "-m", "build") session.run("python", "-m", "build", *session.posargs)
session.log("Building pybind11-global files (PYBIND11_GLOBAL_SDIST=1)") session.log("Building pybind11-global files (PYBIND11_GLOBAL_SDIST=1)")
session.run("python", "-m", "build", env={"PYBIND11_GLOBAL_SDIST": "1"}) session.run(
"python", "-m", "build", *session.posargs, env={"PYBIND11_GLOBAL_SDIST": "1"}
)

View File

@ -42,6 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import contextlib import contextlib
import os import os
import platform import platform
import shlex
import shutil import shutil
import sys import sys
import sysconfig import sysconfig
@ -143,7 +144,12 @@ class Pybind11Extension(_Extension):
if WIN: if WIN:
cflags += ["/EHsc", "/bigobj"] cflags += ["/EHsc", "/bigobj"]
else: else:
cflags += ["-fvisibility=hidden", "-g0"] cflags += ["-fvisibility=hidden"]
env_cflags = os.environ.get("CFLAGS", "")
env_cppflags = os.environ.get("CPPFLAGS", "")
c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags)
if not any(opt.startswith("-g") for opt in c_cpp_flags):
cflags += ["-g0"]
if MACOS: if MACOS:
cflags += ["-stdlib=libc++"] cflags += ["-stdlib=libc++"]
ldflags += ["-stdlib=libc++"] ldflags += ["-stdlib=libc++"]

View File

@ -8,6 +8,7 @@ import pytest
DIR = os.path.abspath(os.path.dirname(__file__)) DIR = os.path.abspath(os.path.dirname(__file__))
MAIN_DIR = os.path.dirname(os.path.dirname(DIR)) MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
@pytest.mark.parametrize("parallel", [False, True]) @pytest.mark.parametrize("parallel", [False, True])
@ -71,13 +72,20 @@ def test_simple_setup_py(monkeypatch, tmpdir, parallel, std):
encoding="ascii", encoding="ascii",
) )
subprocess.check_call( out = subprocess.check_output(
[sys.executable, "setup.py", "build_ext", "--inplace"], [sys.executable, "setup.py", "build_ext", "--inplace"],
stdout=sys.stdout,
stderr=sys.stderr,
) )
if not WIN:
assert b"-g0" in out
out = subprocess.check_output(
[sys.executable, "setup.py", "build_ext", "--inplace", "--force"],
env=dict(os.environ, CFLAGS="-g"),
)
if not WIN:
assert b"-g0" not in out
# Debug helper printout, normally hidden # Debug helper printout, normally hidden
print(out)
for item in tmpdir.listdir(): for item in tmpdir.listdir():
print(item.basename) print(item.basename)

View File

@ -25,7 +25,7 @@ pybind11_enable_warnings(test_embed)
target_link_libraries(test_embed PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads) target_link_libraries(test_embed PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads)
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
file(COPY test_interpreter.py DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") file(COPY test_interpreter.py test_trampoline.py DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
endif() endif()
add_custom_target( add_custom_target(

View File

@ -37,6 +37,22 @@ class PyWidget final : public Widget {
std::string argv0() const override { PYBIND11_OVERRIDE_PURE(std::string, Widget, argv0); } std::string argv0() const override { PYBIND11_OVERRIDE_PURE(std::string, Widget, argv0); }
}; };
class test_override_cache_helper {
public:
virtual int func() { return 0; }
test_override_cache_helper() = default;
virtual ~test_override_cache_helper() = default;
// Non-copyable
test_override_cache_helper &operator=(test_override_cache_helper const &Right) = delete;
test_override_cache_helper(test_override_cache_helper const &Copy) = delete;
};
class test_override_cache_helper_trampoline : public test_override_cache_helper {
int func() override { PYBIND11_OVERRIDE(int, test_override_cache_helper, func); }
};
PYBIND11_EMBEDDED_MODULE(widget_module, m) { PYBIND11_EMBEDDED_MODULE(widget_module, m) {
py::class_<Widget, PyWidget>(m, "Widget") py::class_<Widget, PyWidget>(m, "Widget")
.def(py::init<std::string>()) .def(py::init<std::string>())
@ -45,6 +61,12 @@ PYBIND11_EMBEDDED_MODULE(widget_module, m) {
m.def("add", [](int i, int j) { return i + j; }); m.def("add", [](int i, int j) { return i + j; });
} }
PYBIND11_EMBEDDED_MODULE(trampoline_module, m) {
py::class_<test_override_cache_helper, test_override_cache_helper_trampoline, std::shared_ptr<test_override_cache_helper>>(m, "test_override_cache_helper")
.def(py::init_alias<>())
.def("func", &test_override_cache_helper::func);
}
PYBIND11_EMBEDDED_MODULE(throw_exception, ) { PYBIND11_EMBEDDED_MODULE(throw_exception, ) {
throw std::runtime_error("C++ Error"); throw std::runtime_error("C++ Error");
} }
@ -73,6 +95,33 @@ TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
REQUIRE(cpp_widget.the_answer() == 42); REQUIRE(cpp_widget.the_answer() == 42);
} }
TEST_CASE("Override cache") {
auto module_ = py::module_::import("test_trampoline");
REQUIRE(py::hasattr(module_, "func"));
REQUIRE(py::hasattr(module_, "func2"));
auto locals = py::dict(**module_.attr("__dict__"));
int i = 0;
for (; i < 1500; ++i) {
std::shared_ptr<test_override_cache_helper> p_obj;
std::shared_ptr<test_override_cache_helper> p_obj2;
py::object loc_inst = locals["func"]();
p_obj = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
int ret = p_obj->func();
REQUIRE(ret == 42);
loc_inst = locals["func2"]();
p_obj2 = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
p_obj2->func();
}
}
TEST_CASE("Import error handling") { TEST_CASE("Import error handling") {
REQUIRE_NOTHROW(py::module_::import("widget_module")); REQUIRE_NOTHROW(py::module_::import("widget_module"));
REQUIRE_THROWS_WITH(py::module_::import("throw_exception"), REQUIRE_THROWS_WITH(py::module_::import("throw_exception"),

View File

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
import trampoline_module
def func():
class Test(trampoline_module.test_override_cache_helper):
def func(self):
return 42
return Test()
def func2():
class Test(trampoline_module.test_override_cache_helper):
pass
return Test()

View File

@ -214,6 +214,25 @@ static void test_gil_from_thread() {
t.join(); t.join();
} }
class test_override_cache_helper {
public:
virtual int func() { return 0; }
test_override_cache_helper() = default;
virtual ~test_override_cache_helper() = default;
// Non-copyable
test_override_cache_helper &operator=(test_override_cache_helper const &Right) = delete;
test_override_cache_helper(test_override_cache_helper const &Copy) = delete;
};
class test_override_cache_helper_trampoline : public test_override_cache_helper {
int func() override { PYBIND11_OVERRIDE(int, test_override_cache_helper, func); }
};
inline int test_override_cache(std::shared_ptr<test_override_cache_helper> const &instance) { return instance->func(); }
// Forward declaration (so that we can put the main tests here; the inherited virtual approaches are // Forward declaration (so that we can put the main tests here; the inherited virtual approaches are
// rather long). // rather long).
@ -378,6 +397,12 @@ TEST_SUBMODULE(virtual_functions, m) {
// .def("str_ref", &OverrideTest::str_ref) // .def("str_ref", &OverrideTest::str_ref)
.def("A_value", &OverrideTest::A_value) .def("A_value", &OverrideTest::A_value)
.def("A_ref", &OverrideTest::A_ref); .def("A_ref", &OverrideTest::A_ref);
py::class_<test_override_cache_helper, test_override_cache_helper_trampoline, std::shared_ptr<test_override_cache_helper>>(m, "test_override_cache_helper")
.def(py::init_alias<>())
.def("func", &test_override_cache_helper::func);
m.def("test_override_cache", test_override_cache);
} }

View File

@ -439,3 +439,22 @@ def test_issue_1454():
# Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7) # Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7)
m.test_gil() m.test_gil()
m.test_gil_from_thread() m.test_gil_from_thread()
def test_python_override():
def func():
class Test(m.test_override_cache_helper):
def func(self):
return 42
return Test()
def func2():
class Test(m.test_override_cache_helper):
pass
return Test()
for _ in range(1500):
assert m.test_override_cache(func()) == 42
assert m.test_override_cache(func2()) == 0