mirror of
https://github.com/pybind/pybind11.git
synced 2026-06-04 13:34:24 +08:00
Merge branch 'master' into henryiii-patch-3
This commit is contained in:
@@ -48,7 +48,10 @@ Checks: |
|
||||
readability-misplaced-array-index,
|
||||
readability-non-const-parameter,
|
||||
readability-qualified-auto,
|
||||
readability-redundant-casting,
|
||||
readability-redundant-function-ptr-dereference,
|
||||
readability-redundant-inline-specifier,
|
||||
readability-redundant-member-init,
|
||||
readability-redundant-smartptr-get,
|
||||
readability-redundant-string-cstr,
|
||||
readability-simplify-subscript-expr,
|
||||
|
||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -165,7 +165,7 @@ The valid options are:
|
||||
* Use `cmake --build build -j12` to build with 12 cores (for example).
|
||||
* 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
|
||||
- On Unix, setting `CMAKE_GENERATOR=Ninja` in your environment will give
|
||||
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
|
||||
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -4,12 +4,8 @@ updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
interval: "monthly"
|
||||
groups:
|
||||
actions:
|
||||
patterns:
|
||||
- "*"
|
||||
ignore:
|
||||
- dependency-name: actions/checkout
|
||||
versions:
|
||||
- "<5"
|
||||
|
||||
177
.github/workflows/ci.yml
vendored
177
.github/workflows/ci.yml
vendored
@@ -88,7 +88,7 @@ jobs:
|
||||
cmake-args: -DCMAKE_CXX_STANDARD=20 -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON
|
||||
- runs-on: ubuntu-latest
|
||||
python-version: '3.14'
|
||||
cmake-args: -DCMAKE_CXX_STANDARD=14 -DCMAKE_CXX_FLAGS="-DPYBIND11_HAS_SUBINTERPRETER_SUPPORT=0"
|
||||
cmake-args: -DCMAKE_CXX_STANDARD=14
|
||||
- runs-on: ubuntu-latest
|
||||
python-version: 'pypy-3.10'
|
||||
cmake-args: -DCMAKE_CXX_STANDARD=14
|
||||
@@ -229,6 +229,7 @@ jobs:
|
||||
run: cmake --build . --target pytest
|
||||
|
||||
- name: Compiled tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build . --target cpptest
|
||||
|
||||
- name: Interface test
|
||||
@@ -296,7 +297,7 @@ jobs:
|
||||
|
||||
- name: Valgrind cache
|
||||
if: matrix.valgrind
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
id: cache-valgrind
|
||||
with:
|
||||
path: valgrind
|
||||
@@ -334,6 +335,7 @@ jobs:
|
||||
run: cmake --build --preset default --target pytest
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build --preset default --target cpptest
|
||||
|
||||
- name: Visibility test
|
||||
@@ -393,6 +395,7 @@ jobs:
|
||||
run: cmake --build build --target pytest
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build --target cpptest
|
||||
|
||||
- name: Interface test
|
||||
@@ -470,10 +473,10 @@ jobs:
|
||||
|
||||
|
||||
# Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds
|
||||
ubuntu-nvhpc7:
|
||||
ubuntu-nvhpc:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-22.04
|
||||
name: "🐍 3 • NVHPC 23.5 • C++17 • x64"
|
||||
runs-on: ubuntu-24.04
|
||||
name: "🐍 3 • NVHPC 25.11 • C++17 • x64"
|
||||
timeout-minutes: 90
|
||||
|
||||
env:
|
||||
@@ -482,6 +485,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Clean out unused stuff to save space
|
||||
run: |
|
||||
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc /opt/hostedtoolcache/CodeQL
|
||||
sudo apt-get clean
|
||||
|
||||
- name: Add NVHPC Repo
|
||||
run: |
|
||||
echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \
|
||||
@@ -489,10 +497,11 @@ jobs:
|
||||
|
||||
- name: Install 🐍 3 & NVHPC
|
||||
run: |
|
||||
sudo apt-get update -y && \
|
||||
sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \
|
||||
sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy
|
||||
sudo apt-get install -y --no-install-recommends nvhpc-25-11
|
||||
sudo rm -rf /var/lib/apt/lists/*
|
||||
apt-cache depends nvhpc-25-11
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade pytest
|
||||
|
||||
@@ -502,7 +511,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
source /etc/profile.d/modules.sh
|
||||
module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5
|
||||
module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/25.11
|
||||
cmake -S . -B build -DDOWNLOAD_CATCH=ON \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \
|
||||
@@ -510,12 +519,13 @@ jobs:
|
||||
-DPYBIND11_TEST_FILTER="test_smart_ptr.cpp"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j 2 --verbose
|
||||
run: cmake --build build -j $(nproc) --verbose
|
||||
|
||||
- name: Python tests
|
||||
run: cmake --build build --target pytest
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build --target cpptest
|
||||
|
||||
- name: Interface test
|
||||
@@ -570,6 +580,7 @@ jobs:
|
||||
run: cmake --build build --target pytest
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build --target cpptest
|
||||
|
||||
- name: Interface test
|
||||
@@ -652,6 +663,7 @@ jobs:
|
||||
cmake --build build-11 --target check
|
||||
|
||||
- name: C++ tests C++11
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
set +e; source /opt/intel/oneapi/setvars.sh; set -e
|
||||
cmake --build build-11 --target cpptest
|
||||
@@ -689,6 +701,7 @@ jobs:
|
||||
cmake --build build-17 --target check
|
||||
|
||||
- name: C++ tests C++17
|
||||
timeout-minutes: 3
|
||||
run: |
|
||||
set +e; source /opt/intel/oneapi/setvars.sh; set -e
|
||||
cmake --build build-17 --target cpptest
|
||||
@@ -760,6 +773,7 @@ jobs:
|
||||
run: cmake --build build --target pytest
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build --target cpptest
|
||||
|
||||
- name: Interface test
|
||||
@@ -778,7 +792,8 @@ jobs:
|
||||
timeout-minutes: 90
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1 # v1 is required to run inside docker
|
||||
# v1 required for i386/debian container; pinned to SHA to prevent dependabot updates
|
||||
- uses: actions/checkout@544eadc6bf3d226fd7a7a9f0dc5b5bf7ca0675b9 # v1
|
||||
|
||||
- name: Install requirements
|
||||
run: |
|
||||
@@ -1000,6 +1015,7 @@ jobs:
|
||||
run: cmake --build build --target pytest
|
||||
|
||||
- name: C++20 tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build --target cpptest -j 2
|
||||
|
||||
- name: Interface test C++20
|
||||
@@ -1076,6 +1092,7 @@ jobs:
|
||||
run: cmake --build build --target pytest -j 2
|
||||
|
||||
- name: C++11 tests
|
||||
timeout-minutes: 3
|
||||
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target cpptest -j 2
|
||||
|
||||
- name: Interface test C++11
|
||||
@@ -1100,6 +1117,7 @@ jobs:
|
||||
run: cmake --build build2 --target pytest -j 2
|
||||
|
||||
- name: C++14 tests
|
||||
timeout-minutes: 3
|
||||
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target cpptest -j 2
|
||||
|
||||
- name: Interface test C++14
|
||||
@@ -1124,6 +1142,7 @@ jobs:
|
||||
run: cmake --build build3 --target pytest -j 2
|
||||
|
||||
- name: C++17 tests
|
||||
timeout-minutes: 3
|
||||
run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build3 --target cpptest -j 2
|
||||
|
||||
- name: Interface test C++17
|
||||
@@ -1153,7 +1172,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Clang
|
||||
uses: egor-tensin/setup-clang@v1
|
||||
uses: egor-tensin/setup-clang@v2
|
||||
|
||||
- name: Setup Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v6
|
||||
@@ -1195,6 +1214,7 @@ jobs:
|
||||
run: cmake --build . --target pytest -j 2
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build . --target cpptest -j 2
|
||||
|
||||
- name: Interface test
|
||||
@@ -1205,3 +1225,136 @@ jobs:
|
||||
|
||||
- name: Clean directory
|
||||
run: git clean -fdx
|
||||
|
||||
# Clang with MSVC/Windows SDK toolchain + python.org CPython (Windows ARM)
|
||||
windows_arm_clang_msvc:
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-11-arm]
|
||||
python: ['3.13']
|
||||
|
||||
runs-on: "${{ matrix.os }}"
|
||||
timeout-minutes: 90
|
||||
|
||||
name: "🐍 ${{ matrix.python }} • ${{ matrix.os }} • clang-msvc"
|
||||
|
||||
steps:
|
||||
- name: Show env
|
||||
run: env
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
architecture: arm64
|
||||
|
||||
- name: Run pip installs
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -r tests/requirements.txt
|
||||
|
||||
- name: Configure CMake
|
||||
run: >
|
||||
cmake -G Ninja -S . -B .
|
||||
-DPYBIND11_WERROR=OFF
|
||||
-DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF
|
||||
-DDOWNLOAD_CATCH=ON
|
||||
-DDOWNLOAD_EIGEN=ON
|
||||
-DCMAKE_CXX_COMPILER=clang++
|
||||
-DCMAKE_CXX_STANDARD=20
|
||||
-DPython_ROOT_DIR="$env:Python_ROOT_DIR"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build . -j 2
|
||||
|
||||
- name: Python tests
|
||||
run: cmake --build . --target pytest -j 2
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build . --target cpptest -j 2
|
||||
|
||||
- name: Interface test
|
||||
run: cmake --build . --target test_cmake_build -j 2
|
||||
|
||||
- name: Visibility test
|
||||
run: cmake --build . --target test_cross_module_rtti -j 2
|
||||
|
||||
# Clang in MSYS2/MinGW-w64 CLANGARM64 toolchain + MSYS2 Python (Windows ARM)
|
||||
windows_arm_clang_msys2:
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-11-arm]
|
||||
|
||||
runs-on: "${{ matrix.os }}"
|
||||
timeout-minutes: 90
|
||||
|
||||
name: "${{ matrix.os }} • clang-msys2"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: CLANGARM64
|
||||
update: true
|
||||
install: |
|
||||
mingw-w64-clang-aarch64-cmake
|
||||
mingw-w64-clang-aarch64-clang
|
||||
mingw-w64-clang-aarch64-ninja
|
||||
mingw-w64-clang-aarch64-python-pip
|
||||
mingw-w64-clang-aarch64-python-pytest
|
||||
mingw-w64-clang-aarch64-python-numpy
|
||||
|
||||
- name: Debug info
|
||||
run: |
|
||||
clang++ --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
python --version
|
||||
|
||||
- name: Run pip installs
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install -r tests/requirements.txt
|
||||
|
||||
- name: Configure CMake
|
||||
run: >-
|
||||
cmake -S . -B build
|
||||
-DPYBIND11_WERROR=OFF
|
||||
-DDOWNLOAD_CATCH=ON
|
||||
-DDOWNLOAD_EIGEN=ON
|
||||
-DCMAKE_CXX_COMPILER=clang++
|
||||
-DCMAKE_CXX_STANDARD=20
|
||||
-DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)")
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build -j 2
|
||||
|
||||
- name: Python tests
|
||||
run: cmake --build build --target pytest -j 2
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: PYTHONHOME=/clangarm64 PYTHONPATH=/clangarm64 cmake --build build --target cpptest -j 2
|
||||
|
||||
- name: Interface test
|
||||
run: cmake --build build --target test_cmake_build -j 2
|
||||
|
||||
- name: Visibility test
|
||||
run: cmake --build build --target test_cross_module_rtti -j 2
|
||||
|
||||
4
.github/workflows/configure.yml
vendored
4
.github/workflows/configure.yml
vendored
@@ -39,10 +39,10 @@ jobs:
|
||||
cmake: "3.15"
|
||||
|
||||
- runs-on: macos-14
|
||||
cmake: "4.0"
|
||||
cmake: "4.2"
|
||||
|
||||
- runs-on: windows-latest
|
||||
cmake: "4.0"
|
||||
cmake: "4.2"
|
||||
|
||||
name: 🐍 3.11 • CMake ${{ matrix.cmake }} • ${{ matrix.runs-on }}
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
|
||||
6
.github/workflows/nightlies.yml
vendored
6
.github/workflows/nightlies.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
nox -s build
|
||||
nox -s build_global
|
||||
|
||||
- uses: actions/upload-artifact@v5
|
||||
- uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: Packages
|
||||
path: dist/*
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
needs: [build_wheel]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
run: ls -lha dist/*.whl
|
||||
|
||||
- name: Upload wheel to Anaconda Cloud as nightly
|
||||
uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2
|
||||
uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3
|
||||
with:
|
||||
artifacts_path: dist
|
||||
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
|
||||
|
||||
6
.github/workflows/pip.yml
vendored
6
.github/workflows/pip.yml
vendored
@@ -72,13 +72,13 @@ jobs:
|
||||
run: twine check dist/*
|
||||
|
||||
- name: Save standard package
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: standard
|
||||
path: dist/pybind11-*
|
||||
|
||||
- name: Save global package
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: global
|
||||
path: dist/*global-*
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
# Downloads all to directories matching the artifact names
|
||||
- uses: actions/download-artifact@v6
|
||||
- uses: actions/download-artifact@v7
|
||||
|
||||
- name: Generate artifact attestation for sdist and wheel
|
||||
uses: actions/attest-build-provenance@v3
|
||||
|
||||
1
.github/workflows/reusable-standard.yml
vendored
1
.github/workflows/reusable-standard.yml
vendored
@@ -83,6 +83,7 @@ jobs:
|
||||
run: cmake --build build --target pytest
|
||||
|
||||
- name: C++ tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build --target cpptest
|
||||
|
||||
- name: Interface test
|
||||
|
||||
2
.github/workflows/upstream.yml
vendored
2
.github/workflows/upstream.yml
vendored
@@ -66,6 +66,7 @@ jobs:
|
||||
run: cmake --build build11 --target pytest -j 2
|
||||
|
||||
- name: C++11 tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build11 --target cpptest -j 2
|
||||
|
||||
- name: Interface test C++11
|
||||
@@ -87,6 +88,7 @@ jobs:
|
||||
run: cmake --build build17 --target pytest
|
||||
|
||||
- name: C++17 tests
|
||||
timeout-minutes: 3
|
||||
run: cmake --build build17 --target cpptest
|
||||
|
||||
# Third build - C++17 mode with unstable ABI
|
||||
|
||||
@@ -25,14 +25,14 @@ repos:
|
||||
|
||||
# Clang format the codebase automatically
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: "v21.1.6"
|
||||
rev: "v21.1.8"
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types_or: [c++, c, cuda]
|
||||
|
||||
# Ruff, the Python auto-correcting linter/formatter written in Rust
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.7
|
||||
rev: v0.14.10
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: ["--fix", "--show-fixes"]
|
||||
@@ -40,7 +40,7 @@ repos:
|
||||
|
||||
# Check static types with mypy
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: "v1.19.0"
|
||||
rev: "v1.19.1"
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: []
|
||||
@@ -115,9 +115,18 @@ repos:
|
||||
rev: "v2.4.1"
|
||||
hooks:
|
||||
- id: codespell
|
||||
exclude: ".supp$"
|
||||
exclude: "(.supp|^pyproject.toml)$"
|
||||
args: ["-x.codespell-ignore-lines", "-Lccompiler,intstruct"]
|
||||
|
||||
# Also check spelling
|
||||
# Use mirror because pre-commit autoupdate confuses tags in the upstream repo.
|
||||
# See https://github.com/crate-ci/typos/issues/390
|
||||
- repo: https://github.com/adhtruong/mirrors-typos
|
||||
rev: "v1.41.0"
|
||||
hooks:
|
||||
- id: typos
|
||||
args: []
|
||||
|
||||
# Check for common shell mistakes
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: "v0.11.0.1"
|
||||
@@ -142,7 +151,7 @@ repos:
|
||||
|
||||
# Check schemas on some of our YAML files
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.35.0
|
||||
rev: 0.36.0
|
||||
hooks:
|
||||
- id: check-readthedocs
|
||||
- id: check-github-workflows
|
||||
|
||||
@@ -10,7 +10,7 @@ if(NOT CMAKE_VERSION VERSION_LESS "3.27")
|
||||
cmake_policy(GET CMP0148 _pybind11_cmp0148)
|
||||
endif()
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
if(_pybind11_cmp0148)
|
||||
cmake_policy(SET CMP0148 ${_pybind11_cmp0148})
|
||||
|
||||
@@ -18,7 +18,7 @@ information, see :doc:`/compiling`.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
project(example)
|
||||
|
||||
find_package(pybind11 REQUIRED) # or `add_subdirectory(pybind11)`
|
||||
@@ -302,7 +302,7 @@ Activating a Sub-interpreter
|
||||
|
||||
Once a sub-interpreter is created, you can "activate" it on a thread (and
|
||||
acquire its GIL) by creating a :class:`subinterpreter_scoped_activate`
|
||||
instance and passing it the sub-intepreter to be activated. The function
|
||||
instance and passing it the sub-interpreter to be activated. The function
|
||||
will acquire the sub-interpreter's GIL and make the sub-interpreter the
|
||||
current active interpreter on the current thread for the lifetime of the
|
||||
instance. When the :class:`subinterpreter_scoped_activate` instance goes out
|
||||
@@ -492,4 +492,8 @@ Best Practices for sub-interpreter safety
|
||||
So you must still consider the thread safety of your C++ code. Remember, in Python 3.12
|
||||
sub-interpreters must be destroyed on the same thread that they were created on.
|
||||
|
||||
- When using sub-interpreters in free-threaded python builds, note that creating and destroying
|
||||
sub-interpreters may initiate a "stop-the-world". Be sure to detach long-running C++ threads
|
||||
from Python thread state (similar to releasing the GIL) to avoid deadlocks.
|
||||
|
||||
- Familiarize yourself with :ref:`misc_concurrency`.
|
||||
|
||||
@@ -13,6 +13,161 @@ Changes will be added here periodically from the "Suggested changelog
|
||||
entry" block in pull request descriptions.
|
||||
|
||||
|
||||
## Version 3.0.2 (release date TBD)
|
||||
|
||||
Bug fixes:
|
||||
|
||||
- MSVC 19.16 and earlier were blocked from using `std::launder` due to internal compiler errors.
|
||||
[#5968](https://github.com/pybind/pybind11/pull/5968)
|
||||
|
||||
- Internals destructors were updated to check the owning interpreter before clearing Python objects.
|
||||
[#5965](https://github.com/pybind/pybind11/pull/5965)
|
||||
|
||||
- pybind11 internals were updated to be deallocated during (sub-)interpreter shutdown to avoid memory leaks.
|
||||
[#5958](https://github.com/pybind/pybind11/pull/5958)
|
||||
|
||||
- Fixed ambiguous `str(handle)` construction for `object`-derived types like `kwargs` or `dict` by templatizing the constructor with SFINAE.
|
||||
[#5949](https://github.com/pybind/pybind11/pull/5949)
|
||||
|
||||
- Fixed concurrency consistency for `internals_pp_manager` under multiple-interpreters.
|
||||
[#5947](https://github.com/pybind/pybind11/pull/5947)
|
||||
|
||||
- Fixed MSVC LNK2001 in C++20 builds when /GL (whole program optimization) is enabled.
|
||||
[#5939](https://github.com/pybind/pybind11/pull/5939)
|
||||
|
||||
- Added per-interpreter storage for `gil_safe_call_once_and_store` to make it safe under multi-interpreters.
|
||||
[#5933](https://github.com/pybind/pybind11/pull/5933)
|
||||
|
||||
- A workaround for a GCC `-Warray-bounds` false positive in `argument_vector` was added.
|
||||
[#5908](https://github.com/pybind/pybind11/pull/5908)
|
||||
|
||||
- Corrected a mistake where support for `__index__` was added, but the type hints did not reflect acceptance of `SupportsIndex` objects. Also fixed a long-standing bug: the complex-caster did not accept `__index__` in `convert` mode.
|
||||
[#5891](https://github.com/pybind/pybind11/pull/5891)
|
||||
|
||||
- Fixed `*args/**kwargs` return types. Added type hinting to `py::make_tuple`.
|
||||
[#5881](https://github.com/pybind/pybind11/pull/5881)
|
||||
|
||||
- Fixed compiler error in `type_caster_generic` when casting a `T` implicitly convertible from `T*`.
|
||||
[#5873](https://github.com/pybind/pybind11/pull/5873)
|
||||
|
||||
- Updated `py::native_enum` bindings to unregister enum types on destruction, preventing a use-after-free when returning a destroyed enum instance.
|
||||
[#5871](https://github.com/pybind/pybind11/pull/5871)
|
||||
|
||||
- Fixed undefined behavior that occurred when importing pybind11 modules from non-main threads created by C API modules or embedded python interpreters.
|
||||
[#5870](https://github.com/pybind/pybind11/pull/5870)
|
||||
|
||||
- Fixed dangling pointer in `internals::registered_types_cpp_fast`.
|
||||
[#5867](https://github.com/pybind/pybind11/pull/5867)
|
||||
|
||||
- Added support for `std::shared_ptr<T>` when loading module-local or conduit types from other modules.
|
||||
[#5862](https://github.com/pybind/pybind11/pull/5862)
|
||||
|
||||
- Fixed thread-safety issues if types were concurrently registered while `get_local_type_info()` was called in free threaded Python.
|
||||
[#5856](https://github.com/pybind/pybind11/pull/5856)
|
||||
|
||||
- Fixed py::float_ casting and py::int_ and py::float_ type hints.
|
||||
[#5839](https://github.com/pybind/pybind11/pull/5839)
|
||||
|
||||
- Fixed two `smart_holder` bugs in `shared_ptr` and `unique_ptr` adoption with multiple/virtual inheritance:
|
||||
- `shared_ptr` to-Python caster was updated to register the correct subobject pointer (fixes #5786).
|
||||
- `unique_ptr` adoption was updated to own the proper object start while aliasing subobject pointers for registration, which fixed MSVC crashes during destruction.
|
||||
[#5836](https://github.com/pybind/pybind11/pull/5836)
|
||||
|
||||
- Constrained `accessor::operator=` templates to avoid obscuring special members.
|
||||
[#5832](https://github.com/pybind/pybind11/pull/5832)
|
||||
|
||||
- Fixed crash that can occur when finalizers acquire and release the GIL.
|
||||
[#5828](https://github.com/pybind/pybind11/pull/5828)
|
||||
|
||||
- Fixed compiler detection in `pybind11/detail/pybind11_namespace_macros.h` for clang-cl on Windows, to address warning suppression macros.
|
||||
[#5816](https://github.com/pybind/pybind11/pull/5816)
|
||||
|
||||
- Fixed compatibility with CMake policy CMP0190 by not always requiring a Python interpreter when cross-compiling.
|
||||
[#5829](https://github.com/pybind/pybind11/pull/5829)
|
||||
|
||||
- Added a static assertion to disallow `keep_alive` and `call_guard` on properties.
|
||||
[#5533](https://github.com/pybind/pybind11/pull/5533)
|
||||
|
||||
Internal:
|
||||
|
||||
- CMake policy limit was set to 4.1.
|
||||
[#5944](https://github.com/pybind/pybind11/pull/5944)
|
||||
|
||||
- Improved performance of function calls between Python and C++ by switching to the "vectorcall" calling protocol.
|
||||
[#5948](https://github.com/pybind/pybind11/pull/5948)
|
||||
|
||||
- Many C-style casts were replaced with C++-style casts.
|
||||
[#5930](https://github.com/pybind/pybind11/pull/5930)
|
||||
|
||||
- Added `cast_sources` abstraction to `type_caster_generic`.
|
||||
[#5866](https://github.com/pybind/pybind11/pull/5866)
|
||||
|
||||
- Improved the performance of from-Python conversions of legacy pybind11 enum objects bound by `py::enum_`.
|
||||
[#5860](https://github.com/pybind/pybind11/pull/5860)
|
||||
|
||||
- Reduced size overhead by deduplicating functions' readable signatures and type information.
|
||||
[#5857](https://github.com/pybind/pybind11/pull/5857)
|
||||
|
||||
- Used new Python 3.14 C APIs when available.
|
||||
[#5854](https://github.com/pybind/pybind11/pull/5854)
|
||||
|
||||
- Improved performance of function dispatch and type casting by porting two-level type info lookup strategy from nanobind.
|
||||
[#5842](https://github.com/pybind/pybind11/pull/5842)
|
||||
|
||||
- Updated `.gitignore` to exclude `__pycache__/` directories.
|
||||
[#5838](https://github.com/pybind/pybind11/pull/5838)
|
||||
|
||||
- Changed internals to use `thread_local` instead of `thread_specific_storage` for increased performance.
|
||||
[#5834](https://github.com/pybind/pybind11/pull/5834)
|
||||
|
||||
- Reduced function call overhead by using thread_local for loader_life_support when possible.
|
||||
[#5830](https://github.com/pybind/pybind11/pull/5830)
|
||||
|
||||
- Removed heap allocation for the C++ argument array when dispatching functions with 6 or fewer arguments.
|
||||
[#5824](https://github.com/pybind/pybind11/pull/5824)
|
||||
|
||||
|
||||
Documentation:
|
||||
|
||||
- Fixed docstring for `long double` complex types to use `numpy.clongdouble` instead of the deprecated `numpy.longcomplex` (removed in NumPy 2.0).
|
||||
[#5952](https://github.com/pybind/pybind11/pull/5952)
|
||||
|
||||
- The "Supported compilers" and "Supported platforms" sections in the main `README.rst` were replaced with a new "Supported platforms & compilers" section that points to the CI test matrix as the living source of truth.
|
||||
[#5910](https://github.com/pybind/pybind11/pull/5910)
|
||||
|
||||
- Fixed documentation formatting.
|
||||
[#5903](https://github.com/pybind/pybind11/pull/5903)
|
||||
|
||||
- Updated upgrade notes for `py::native_enum`.
|
||||
[#5885](https://github.com/pybind/pybind11/pull/5885)
|
||||
|
||||
- Clarified in the docs to what extent bindings are global.
|
||||
[#5859](https://github.com/pybind/pybind11/pull/5859)
|
||||
|
||||
|
||||
Tests:
|
||||
|
||||
- Calls to `env.deprecated_call()` were replaced with direct calls to `pytest.deprecated_call()`.
|
||||
[#5893](https://github.com/pybind/pybind11/pull/5893)
|
||||
|
||||
- Updated pytest configuration to use `log_level` instead of `log_cli_level`.
|
||||
[#5890](https://github.com/pybind/pybind11/pull/5890)
|
||||
|
||||
|
||||
CI:
|
||||
|
||||
- Added CI tests for windows-11-arm with clang/MSVC (currently python 3.13), windows-11-arm with clang/mingw (currently python 3.12).
|
||||
[#5932](https://github.com/pybind/pybind11/pull/5932)
|
||||
|
||||
- These clang-tidy rules were added: `readability-redundant-casting`, `readability-redundant-inline-specifier`, `readability-redundant-member-init`
|
||||
[#5924](https://github.com/pybind/pybind11/pull/5924)
|
||||
|
||||
- Replaced deprecated macos-13 runners with macos-15-intel in CI.
|
||||
[#5916](https://github.com/pybind/pybind11/pull/5916)
|
||||
|
||||
- Restored `runs-on: windows-latest` in CI.
|
||||
[#5835](https://github.com/pybind/pybind11/pull/5835)
|
||||
|
||||
## Version 3.0.1 (August 22, 2025)
|
||||
|
||||
Bug fixes:
|
||||
@@ -167,7 +322,7 @@ New Features:
|
||||
[#5665](https://github.com/pybind/pybind11/pull/5665) and consolidate code
|
||||
[#5670](https://github.com/pybind/pybind11/pull/5670).
|
||||
|
||||
- Added API in `pybind11/subinterpreter.h` for embedding sub-intepreters (requires Python 3.12+).
|
||||
- Added API in `pybind11/subinterpreter.h` for embedding sub-interpreters (requires Python 3.12+).
|
||||
[#5666](https://github.com/pybind/pybind11/pull/5666)
|
||||
|
||||
- `py::native_enum` was added, for conversions between Python's native
|
||||
@@ -1213,7 +1368,7 @@ Performance and style:
|
||||
- Optimize Eigen sparse matrix casting by removing unnecessary
|
||||
temporary. [#4064](https://github.com/pybind/pybind11/pull/4064)
|
||||
- Avoid potential implicit copy/assignment constructors causing double
|
||||
free in `strdup_gaurd`.
|
||||
free in `strdup_guard`.
|
||||
[#3905](https://github.com/pybind/pybind11/pull/3905)
|
||||
- Enable clang-tidy checks `misc-definitions-in-headers`,
|
||||
`modernize-loop-convert`, and `modernize-use-nullptr`.
|
||||
|
||||
@@ -18,7 +18,7 @@ A Python extension module can be created with just a few lines of code:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
set(PYBIND11_FINDPYTHON ON)
|
||||
@@ -447,7 +447,7 @@ See the `Config file`_ docstring for details of relevant CMake variables.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(pybind11 REQUIRED)
|
||||
@@ -492,7 +492,7 @@ FindPython, pybind11 will detect this and use the existing targets instead:
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED)
|
||||
@@ -570,7 +570,7 @@ You can use these targets to build complex applications. For example, the
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
|
||||
@@ -628,7 +628,7 @@ information about usage in C++, see :doc:`/advanced/embedding`.
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
project(example LANGUAGES CXX)
|
||||
|
||||
find_package(pybind11 REQUIRED) # or add_subdirectory(pybind11)
|
||||
|
||||
@@ -23,8 +23,6 @@ idna==3.7
|
||||
# via requests
|
||||
imagesize==1.4.1
|
||||
# via sphinx
|
||||
importlib-metadata==8.7.0
|
||||
# via sphinx
|
||||
jinja2==3.1.6
|
||||
# via
|
||||
# myst-parser
|
||||
@@ -85,7 +83,5 @@ sphinxcontrib-serializinghtml==1.1.10
|
||||
# via sphinx
|
||||
sphinxcontrib-svg2pdfconverter==1.2.2
|
||||
# via -r requirements.in
|
||||
urllib3==2.5.0
|
||||
urllib3==2.6.3
|
||||
# via requests
|
||||
zipp==3.23.0
|
||||
# via importlib-metadata
|
||||
|
||||
@@ -373,7 +373,7 @@ struct type_record {
|
||||
+ (base_has_unique_ptr_holder ? "does not" : "does"));
|
||||
}
|
||||
|
||||
bases.append((PyObject *) base_info->type);
|
||||
bases.append(reinterpret_cast<PyObject *>(base_info->type));
|
||||
|
||||
#ifdef PYBIND11_BACKWARD_COMPATIBILITY_TP_DICTOFFSET
|
||||
dynamic_attr |= base_info->type->tp_dictoffset != 0;
|
||||
@@ -721,7 +721,9 @@ template <typename... Extra,
|
||||
size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
|
||||
constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
|
||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(nargs, has_args, has_kwargs);
|
||||
return named == 0 || (self + named + size_t(has_args) + size_t(has_kwargs)) == nargs;
|
||||
return named == 0
|
||||
|| (self + named + static_cast<size_t>(has_args) + static_cast<size_t>(has_kwargs))
|
||||
== nargs;
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
@@ -66,10 +66,11 @@ struct buffer_info {
|
||||
bool readonly = false)
|
||||
: ptr(ptr), itemsize(itemsize), size(1), format(format), ndim(ndim),
|
||||
shape(std::move(shape_in)), strides(std::move(strides_in)), readonly(readonly) {
|
||||
if (ndim != (ssize_t) shape.size() || ndim != (ssize_t) strides.size()) {
|
||||
if (ndim != static_cast<ssize_t>(shape.size())
|
||||
|| ndim != static_cast<ssize_t>(strides.size())) {
|
||||
pybind11_fail("buffer_info: ndim doesn't match shape and/or strides length");
|
||||
}
|
||||
for (size_t i = 0; i < (size_t) ndim; ++i) {
|
||||
for (size_t i = 0; i < static_cast<size_t>(ndim); ++i) {
|
||||
size *= shape[i];
|
||||
}
|
||||
}
|
||||
@@ -195,7 +196,7 @@ struct compare_buffer_info {
|
||||
template <typename T>
|
||||
struct compare_buffer_info<T, detail::enable_if_t<std::is_integral<T>::value>> {
|
||||
static bool compare(const buffer_info &b) {
|
||||
return (size_t) b.itemsize == sizeof(T)
|
||||
return static_cast<size_t>(b.itemsize) == sizeof(T)
|
||||
&& (b.format == format_descriptor<T>::value
|
||||
|| ((sizeof(T) == sizeof(long))
|
||||
&& b.format == (std::is_unsigned<T>::value ? "L" : "l"))
|
||||
|
||||
@@ -394,7 +394,8 @@ public:
|
||||
}
|
||||
|
||||
/* Check if this is a C++ type */
|
||||
const auto &bases = all_type_info((PyTypeObject *) type::handle_of(h).ptr());
|
||||
const auto &bases
|
||||
= all_type_info(reinterpret_cast<PyTypeObject *>(type::handle_of(h).ptr()));
|
||||
if (bases.size() == 1) { // Only allowing loading from a single-value type
|
||||
value = values_and_holders(reinterpret_cast<instance *>(h.ptr())).begin()->value_ptr();
|
||||
return true;
|
||||
@@ -475,7 +476,7 @@ public:
|
||||
|
||||
private:
|
||||
// Test if an object is a NumPy boolean (without fetching the type).
|
||||
static inline bool is_numpy_bool(handle object) {
|
||||
static bool is_numpy_bool(handle object) {
|
||||
const char *type_name = Py_TYPE(object.ptr())->tp_name;
|
||||
// Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support
|
||||
return std::strcmp("numpy.bool", type_name) == 0
|
||||
@@ -541,7 +542,7 @@ struct string_caster {
|
||||
|
||||
const auto *buffer
|
||||
= reinterpret_cast<const CharT *>(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr()));
|
||||
size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT);
|
||||
size_t length = static_cast<size_t>(PYBIND11_BYTES_SIZE(utfNbytes.ptr())) / sizeof(CharT);
|
||||
// Skip BOM for UTF-16/32
|
||||
if (UTF_N > 8) {
|
||||
buffer++;
|
||||
@@ -2077,8 +2078,7 @@ using is_pos_only = std::is_same<intrinsic_t<T>, pos_only>;
|
||||
// forward declaration (definition in attr.h)
|
||||
struct function_record;
|
||||
|
||||
/// (Inline size chosen mostly arbitrarily; 6 should pad function_call out to two cache lines
|
||||
/// (16 pointers) in size.)
|
||||
/// Inline size chosen mostly arbitrarily.
|
||||
constexpr std::size_t arg_vector_small_size = 6;
|
||||
|
||||
/// Internal data associated with a single function call
|
||||
@@ -2190,86 +2190,121 @@ private:
|
||||
std::tuple<make_caster<Args>...> argcasters;
|
||||
};
|
||||
|
||||
/// Helper class which collects only positional arguments for a Python function call.
|
||||
/// A fancier version below can collect any argument, but this one is optimal for simple calls.
|
||||
template <return_value_policy policy>
|
||||
class simple_collector {
|
||||
public:
|
||||
template <typename... Ts>
|
||||
explicit simple_collector(Ts &&...values)
|
||||
: m_args(pybind11::make_tuple<policy>(std::forward<Ts>(values)...)) {}
|
||||
|
||||
const tuple &args() const & { return m_args; }
|
||||
dict kwargs() const { return {}; }
|
||||
|
||||
tuple args() && { return std::move(m_args); }
|
||||
|
||||
/// Call a Python function and pass the collected arguments
|
||||
object call(PyObject *ptr) const {
|
||||
PyObject *result = PyObject_CallObject(ptr, m_args.ptr());
|
||||
if (!result) {
|
||||
throw error_already_set();
|
||||
}
|
||||
return reinterpret_steal<object>(result);
|
||||
}
|
||||
|
||||
private:
|
||||
tuple m_args;
|
||||
};
|
||||
// [workaround(intel)] Separate function required here
|
||||
// We need to put this into a separate function because the Intel compiler
|
||||
// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
|
||||
// (tested with ICC 2021.1 Beta 20200827).
|
||||
template <typename... Args>
|
||||
constexpr bool args_has_keyword_or_ds() {
|
||||
return any_of<is_keyword_or_ds<Args>...>::value;
|
||||
}
|
||||
|
||||
/// Helper class which collects positional, keyword, * and ** arguments for a Python function call
|
||||
template <return_value_policy policy>
|
||||
class unpacking_collector {
|
||||
public:
|
||||
template <typename... Ts>
|
||||
explicit unpacking_collector(Ts &&...values) {
|
||||
// Tuples aren't (easily) resizable so a list is needed for collection,
|
||||
// but the actual function call strictly requires a tuple.
|
||||
auto args_list = list();
|
||||
using expander = int[];
|
||||
(void) expander{0, (process(args_list, std::forward<Ts>(values)), 0)...};
|
||||
explicit unpacking_collector(Ts &&...values)
|
||||
: m_names(reinterpret_steal<tuple>(
|
||||
handle())) // initialize to null to avoid useless allocation of 0-length tuple
|
||||
{
|
||||
/*
|
||||
Python can sometimes utilize an extra space before the arguments to prepend `self`.
|
||||
This is important enough that there is a special flag for it:
|
||||
PY_VECTORCALL_ARGUMENTS_OFFSET.
|
||||
All we have to do is allocate an extra space at the beginning of this array, and set the
|
||||
flag. Note that the extra space is not passed directly in to vectorcall.
|
||||
*/
|
||||
m_args.reserve(sizeof...(values) + 1);
|
||||
m_args.push_back_null();
|
||||
|
||||
m_args = std::move(args_list);
|
||||
if (args_has_keyword_or_ds<Ts...>()) {
|
||||
list names_list;
|
||||
|
||||
// collect_arguments guarantees this can't be constructed with kwargs before the last
|
||||
// positional so we don't need to worry about Ts... being in anything but normal python
|
||||
// order.
|
||||
using expander = int[];
|
||||
(void) expander{0, (process(names_list, std::forward<Ts>(values)), 0)...};
|
||||
|
||||
m_names = reinterpret_steal<tuple>(PyList_AsTuple(names_list.ptr()));
|
||||
} else {
|
||||
auto not_used
|
||||
= reinterpret_steal<list>(handle()); // initialize as null (to avoid an allocation)
|
||||
|
||||
using expander = int[];
|
||||
(void) expander{0, (process(not_used, std::forward<Ts>(values)), 0)...};
|
||||
}
|
||||
}
|
||||
|
||||
const tuple &args() const & { return m_args; }
|
||||
const dict &kwargs() const & { return m_kwargs; }
|
||||
|
||||
tuple args() && { return std::move(m_args); }
|
||||
dict kwargs() && { return std::move(m_kwargs); }
|
||||
|
||||
/// Call a Python function and pass the collected arguments
|
||||
object call(PyObject *ptr) const {
|
||||
PyObject *result = PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr());
|
||||
size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
|
||||
if (m_names) {
|
||||
nargs -= m_names.size();
|
||||
}
|
||||
PyObject *result = _PyObject_Vectorcall(
|
||||
ptr, m_args.data() + 1, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, m_names.ptr());
|
||||
if (!result) {
|
||||
throw error_already_set();
|
||||
}
|
||||
return reinterpret_steal<object>(result);
|
||||
}
|
||||
|
||||
tuple args() const {
|
||||
size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
|
||||
if (m_names) {
|
||||
nargs -= m_names.size();
|
||||
}
|
||||
tuple val(nargs);
|
||||
for (size_t i = 0; i < nargs; ++i) {
|
||||
// +1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
|
||||
val[i] = reinterpret_borrow<object>(m_args[i + 1]);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
dict kwargs() const {
|
||||
dict val;
|
||||
if (m_names) {
|
||||
size_t offset = m_args.size() - m_names.size();
|
||||
for (size_t i = 0; i < m_names.size(); ++i, ++offset) {
|
||||
val[m_names[i]] = reinterpret_borrow<object>(m_args[offset]);
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private:
|
||||
// normal argument, possibly needing conversion
|
||||
template <typename T>
|
||||
void process(list &args_list, T &&x) {
|
||||
auto o = reinterpret_steal<object>(
|
||||
detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
|
||||
if (!o) {
|
||||
void process(list & /*names_list*/, T &&x) {
|
||||
handle h = detail::make_caster<T>::cast(std::forward<T>(x), policy, {});
|
||||
if (!h) {
|
||||
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
|
||||
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
|
||||
throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1));
|
||||
#else
|
||||
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
|
||||
throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1),
|
||||
type_id<T>());
|
||||
#endif
|
||||
}
|
||||
args_list.append(std::move(o));
|
||||
m_args.push_back_steal(h.ptr()); // cast returns a new reference
|
||||
}
|
||||
|
||||
void process(list &args_list, detail::args_proxy ap) {
|
||||
// * unpacking
|
||||
void process(list & /*names_list*/, detail::args_proxy ap) {
|
||||
if (!ap) {
|
||||
return;
|
||||
}
|
||||
for (auto a : ap) {
|
||||
args_list.append(a);
|
||||
m_args.push_back_borrow(a.ptr());
|
||||
}
|
||||
}
|
||||
|
||||
void process(list & /*args_list*/, arg_v a) {
|
||||
// named argument
|
||||
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
||||
void process(list &names_list, arg_v a) {
|
||||
assert(names_list);
|
||||
if (!a.name) {
|
||||
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
|
||||
nameless_argument_error();
|
||||
@@ -2277,7 +2312,8 @@ private:
|
||||
nameless_argument_error(a.type);
|
||||
#endif
|
||||
}
|
||||
if (m_kwargs.contains(a.name)) {
|
||||
auto name = str(a.name);
|
||||
if (names_list.contains(name)) {
|
||||
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
|
||||
multiple_values_error();
|
||||
#else
|
||||
@@ -2291,22 +2327,27 @@ private:
|
||||
throw cast_error_unable_to_convert_call_arg(a.name, a.type);
|
||||
#endif
|
||||
}
|
||||
m_kwargs[a.name] = std::move(a.value);
|
||||
names_list.append(std::move(name));
|
||||
m_args.push_back_borrow(a.value.ptr());
|
||||
}
|
||||
|
||||
void process(list & /*args_list*/, detail::kwargs_proxy kp) {
|
||||
// ** unpacking
|
||||
void process(list &names_list, detail::kwargs_proxy kp) {
|
||||
if (!kp) {
|
||||
return;
|
||||
}
|
||||
for (auto k : reinterpret_borrow<dict>(kp)) {
|
||||
if (m_kwargs.contains(k.first)) {
|
||||
assert(names_list);
|
||||
for (auto &&k : reinterpret_borrow<dict>(kp)) {
|
||||
auto name = str(k.first);
|
||||
if (names_list.contains(name)) {
|
||||
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
|
||||
multiple_values_error();
|
||||
#else
|
||||
multiple_values_error(str(k.first));
|
||||
multiple_values_error(name);
|
||||
#endif
|
||||
}
|
||||
m_kwargs[k.first] = k.second;
|
||||
names_list.append(std::move(name));
|
||||
m_args.push_back_borrow(k.second.ptr());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2332,39 +2373,20 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
tuple m_args;
|
||||
dict m_kwargs;
|
||||
ref_small_vector<arg_vector_small_size> m_args;
|
||||
tuple m_names;
|
||||
};
|
||||
|
||||
// [workaround(intel)] Separate function required here
|
||||
// We need to put this into a separate function because the Intel compiler
|
||||
// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
|
||||
// (tested with ICC 2021.1 Beta 20200827).
|
||||
template <typename... Args>
|
||||
constexpr bool args_are_all_positional() {
|
||||
return all_of<is_positional<Args>...>::value;
|
||||
}
|
||||
|
||||
/// Collect only positional arguments for a Python function call
|
||||
template <return_value_policy policy,
|
||||
typename... Args,
|
||||
typename = enable_if_t<args_are_all_positional<Args...>()>>
|
||||
simple_collector<policy> collect_arguments(Args &&...args) {
|
||||
return simple_collector<policy>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/// Collect all arguments, including keywords and unpacking (only instantiated when needed)
|
||||
template <return_value_policy policy,
|
||||
typename... Args,
|
||||
typename = enable_if_t<!args_are_all_positional<Args...>()>>
|
||||
/// Collect all arguments, including keywords and unpacking
|
||||
template <return_value_policy policy, typename... Args>
|
||||
unpacking_collector<policy> collect_arguments(Args &&...args) {
|
||||
// Following argument order rules for generalized unpacking according to PEP 448
|
||||
static_assert(constexpr_last<is_positional, Args...>()
|
||||
< constexpr_first<is_keyword_or_ds, Args...>()
|
||||
&& constexpr_last<is_s_unpacking, Args...>()
|
||||
< constexpr_first<is_ds_unpacking, Args...>(),
|
||||
"Invalid function call: positional args must precede keywords and ** unpacking; "
|
||||
"* unpacking must precede ** unpacking");
|
||||
static_assert(
|
||||
constexpr_last<is_positional, Args...>() < constexpr_first<is_keyword_or_ds, Args...>(),
|
||||
"Invalid function call: positional args must precede keywords and */** unpacking;");
|
||||
static_assert(constexpr_last<is_s_unpacking, Args...>()
|
||||
< constexpr_first<is_ds_unpacking, Args...>(),
|
||||
"Invalid function call: * unpacking must precede ** unpacking");
|
||||
return unpacking_collector<policy>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,24 +66,23 @@ union inline_array_or_vector {
|
||||
inline_array iarray;
|
||||
heap_vector hvector;
|
||||
|
||||
static_assert(std::is_trivially_move_constructible<ArrayT>::value,
|
||||
"ArrayT must be trivially move constructible");
|
||||
static_assert(std::is_trivially_destructible<ArrayT>::value,
|
||||
"ArrayT must be trivially destructible");
|
||||
|
||||
inline_array_or_vector() : iarray() {}
|
||||
|
||||
~inline_array_or_vector() {
|
||||
if (!is_inline()) {
|
||||
if (is_inline()) {
|
||||
iarray.~inline_array();
|
||||
} else {
|
||||
hvector.~heap_vector();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable copy ctor and assignment.
|
||||
inline_array_or_vector(const inline_array_or_vector &) = delete;
|
||||
inline_array_or_vector &operator=(const inline_array_or_vector &) = delete;
|
||||
|
||||
inline_array_or_vector(inline_array_or_vector &&rhs) noexcept {
|
||||
if (rhs.is_inline()) {
|
||||
std::memcpy(&iarray, &rhs.iarray, sizeof(iarray));
|
||||
new (&iarray) inline_array(std::move(rhs.iarray));
|
||||
} else {
|
||||
new (&hvector) heap_vector(std::move(rhs.hvector));
|
||||
}
|
||||
@@ -95,17 +94,16 @@ union inline_array_or_vector {
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (rhs.is_inline()) {
|
||||
if (!is_inline()) {
|
||||
hvector.~heap_vector();
|
||||
}
|
||||
std::memcpy(&iarray, &rhs.iarray, sizeof(iarray));
|
||||
if (is_inline()) {
|
||||
iarray.~inline_array();
|
||||
} else {
|
||||
if (is_inline()) {
|
||||
new (&hvector) heap_vector(std::move(rhs.hvector));
|
||||
} else {
|
||||
hvector = std::move(rhs.hvector);
|
||||
}
|
||||
hvector.~heap_vector();
|
||||
}
|
||||
|
||||
if (rhs.is_inline()) {
|
||||
new (&iarray) inline_array(std::move(rhs.iarray));
|
||||
} else {
|
||||
new (&hvector) heap_vector(std::move(rhs.hvector));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -126,18 +124,16 @@ union inline_array_or_vector {
|
||||
}
|
||||
};
|
||||
|
||||
// small_vector-like container to avoid heap allocation for N or fewer
|
||||
// arguments.
|
||||
template <std::size_t N>
|
||||
struct argument_vector {
|
||||
template <typename T, std::size_t InlineSize>
|
||||
struct small_vector {
|
||||
public:
|
||||
argument_vector() = default;
|
||||
small_vector() = default;
|
||||
|
||||
// Disable copy ctor and assignment.
|
||||
argument_vector(const argument_vector &) = delete;
|
||||
argument_vector &operator=(const argument_vector &) = delete;
|
||||
argument_vector(argument_vector &&) noexcept = default;
|
||||
argument_vector &operator=(argument_vector &&) noexcept = default;
|
||||
small_vector(const small_vector &) = delete;
|
||||
small_vector &operator=(const small_vector &) = delete;
|
||||
small_vector(small_vector &&) noexcept = default;
|
||||
small_vector &operator=(small_vector &&) noexcept = default;
|
||||
|
||||
std::size_t size() const {
|
||||
if (is_inline()) {
|
||||
@@ -146,7 +142,14 @@ public:
|
||||
return m_repr.hvector.vec.size();
|
||||
}
|
||||
|
||||
handle &operator[](std::size_t idx) {
|
||||
T const *data() const {
|
||||
if (is_inline()) {
|
||||
return m_repr.iarray.arr.data();
|
||||
}
|
||||
return m_repr.hvector.vec.data();
|
||||
}
|
||||
|
||||
T &operator[](std::size_t idx) {
|
||||
assert(idx < size());
|
||||
if (is_inline()) {
|
||||
return m_repr.iarray.arr[idx];
|
||||
@@ -154,7 +157,7 @@ public:
|
||||
return m_repr.hvector.vec[idx];
|
||||
}
|
||||
|
||||
handle operator[](std::size_t idx) const {
|
||||
T const &operator[](std::size_t idx) const {
|
||||
assert(idx < size());
|
||||
if (is_inline()) {
|
||||
return m_repr.iarray.arr[idx];
|
||||
@@ -162,28 +165,28 @@ public:
|
||||
return m_repr.hvector.vec[idx];
|
||||
}
|
||||
|
||||
void push_back(handle x) {
|
||||
void push_back(const T &x) { emplace_back(x); }
|
||||
|
||||
void push_back(T &&x) { emplace_back(std::move(x)); }
|
||||
|
||||
template <typename... Args>
|
||||
void emplace_back(Args &&...x) {
|
||||
if (is_inline()) {
|
||||
auto &ha = m_repr.iarray;
|
||||
if (ha.size == N) {
|
||||
move_to_heap_vector_with_reserved_size(N + 1);
|
||||
push_back_slow_path(x);
|
||||
if (ha.size == InlineSize) {
|
||||
move_to_heap_vector_with_reserved_size(InlineSize + 1);
|
||||
m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
|
||||
} else {
|
||||
ha.arr[ha.size++] = x;
|
||||
ha.arr[ha.size++] = T(std::forward<Args>(x)...);
|
||||
}
|
||||
} else {
|
||||
push_back_slow_path(x);
|
||||
m_repr.hvector.vec.emplace_back(std::forward<Args>(x)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
void emplace_back(Arg &&x) {
|
||||
push_back(handle(x));
|
||||
}
|
||||
|
||||
void reserve(std::size_t sz) {
|
||||
if (is_inline()) {
|
||||
if (sz > N) {
|
||||
if (sz > InlineSize) {
|
||||
move_to_heap_vector_with_reserved_size(sz);
|
||||
}
|
||||
} else {
|
||||
@@ -192,7 +195,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
using repr_type = inline_array_or_vector<handle, N>;
|
||||
using repr_type = inline_array_or_vector<T, InlineSize>;
|
||||
repr_type m_repr;
|
||||
|
||||
PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
|
||||
@@ -201,37 +204,38 @@ private:
|
||||
using heap_vector = typename repr_type::heap_vector;
|
||||
heap_vector hv;
|
||||
hv.vec.reserve(reserved_size);
|
||||
std::copy(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
|
||||
static_assert(std::is_nothrow_move_constructible<T>::value,
|
||||
"this conversion is not exception safe");
|
||||
static_assert(std::is_nothrow_move_constructible<heap_vector>::value,
|
||||
"this conversion is not exception safe");
|
||||
std::move(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
|
||||
new (&m_repr.hvector) heap_vector(std::move(hv));
|
||||
}
|
||||
|
||||
PYBIND11_NOINLINE void push_back_slow_path(handle x) { m_repr.hvector.vec.push_back(x); }
|
||||
|
||||
PYBIND11_NOINLINE void reserve_slow_path(std::size_t sz) { m_repr.hvector.vec.reserve(sz); }
|
||||
|
||||
bool is_inline() const { return m_repr.is_inline(); }
|
||||
};
|
||||
|
||||
// small_vector-like container to avoid heap allocation for N or fewer
|
||||
// arguments.
|
||||
// Container to avoid heap allocation for kRequestedInlineSize or fewer booleans.
|
||||
template <std::size_t kRequestedInlineSize>
|
||||
struct args_convert_vector {
|
||||
struct small_vector<bool, kRequestedInlineSize> {
|
||||
private:
|
||||
public:
|
||||
args_convert_vector() = default;
|
||||
small_vector() = default;
|
||||
|
||||
// Disable copy ctor and assignment.
|
||||
args_convert_vector(const args_convert_vector &) = delete;
|
||||
args_convert_vector &operator=(const args_convert_vector &) = delete;
|
||||
args_convert_vector(args_convert_vector &&) noexcept = default;
|
||||
args_convert_vector &operator=(args_convert_vector &&) noexcept = default;
|
||||
small_vector(const small_vector &) = delete;
|
||||
small_vector &operator=(const small_vector &) = delete;
|
||||
small_vector(small_vector &&) noexcept = default;
|
||||
small_vector &operator=(small_vector &&) noexcept = default;
|
||||
|
||||
args_convert_vector(std::size_t count, bool value) {
|
||||
small_vector(std::size_t count, bool value) {
|
||||
if (count > kInlineSize) {
|
||||
new (&m_repr.hvector) typename repr_type::heap_vector(count, value);
|
||||
} else {
|
||||
auto &inline_arr = m_repr.iarray;
|
||||
inline_arr.arr.fill(value ? std::size_t(-1) : 0);
|
||||
inline_arr.arr.fill(value ? static_cast<std::size_t>(-1) : 0);
|
||||
inline_arr.size = static_cast<decltype(inline_arr.size)>(count);
|
||||
}
|
||||
}
|
||||
@@ -273,9 +277,9 @@ public:
|
||||
assert(wbi.word < kWords);
|
||||
assert(wbi.bit < kBitsPerWord);
|
||||
if (b) {
|
||||
ha.arr[wbi.word] |= (std::size_t(1) << wbi.bit);
|
||||
ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
|
||||
} else {
|
||||
ha.arr[wbi.word] &= ~(std::size_t(1) << wbi.bit);
|
||||
ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
|
||||
}
|
||||
assert(operator[](ha.size - 1) == b);
|
||||
}
|
||||
@@ -284,7 +288,24 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void swap(args_convert_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
|
||||
void set(std::size_t idx, bool value = true) {
|
||||
if (is_inline()) {
|
||||
auto &ha = m_repr.iarray;
|
||||
assert(ha.size < kInlineSize);
|
||||
const auto wbi = word_and_bit_index(idx);
|
||||
assert(wbi.word < kWords);
|
||||
assert(wbi.bit < kBitsPerWord);
|
||||
if (value) {
|
||||
ha.arr[wbi.word] |= (static_cast<std::size_t>(1) << wbi.bit);
|
||||
} else {
|
||||
ha.arr[wbi.word] &= ~(static_cast<std::size_t>(1) << wbi.bit);
|
||||
}
|
||||
} else {
|
||||
m_repr.hvector.vec[idx] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void swap(small_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
|
||||
|
||||
private:
|
||||
struct WordAndBitIndex {
|
||||
@@ -300,7 +321,7 @@ private:
|
||||
const auto wbi = word_and_bit_index(idx);
|
||||
assert(wbi.word < kWords);
|
||||
assert(wbi.bit < kBitsPerWord);
|
||||
return m_repr.iarray.arr[wbi.word] & (std::size_t(1) << wbi.bit);
|
||||
return m_repr.iarray.arr[wbi.word] & (static_cast<std::size_t>(1) << wbi.bit);
|
||||
}
|
||||
|
||||
PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
|
||||
@@ -326,5 +347,71 @@ private:
|
||||
bool is_inline() const { return m_repr.is_inline(); }
|
||||
};
|
||||
|
||||
// Container to avoid heap allocation for N or fewer arguments.
|
||||
template <size_t N>
|
||||
using argument_vector = small_vector<handle, N>;
|
||||
|
||||
// Container to avoid heap allocation for N or fewer booleans.
|
||||
template <size_t N>
|
||||
using args_convert_vector = small_vector<bool, N>;
|
||||
|
||||
/// A small_vector of PyObject* that holds references and releases them on destruction.
|
||||
/// This provides explicit ownership semantics without relying on py::object's
|
||||
/// destructor, and avoids the need for reinterpret_cast when passing to vectorcall.
|
||||
template <std::size_t InlineSize>
|
||||
class ref_small_vector {
|
||||
public:
|
||||
ref_small_vector() = default;
|
||||
|
||||
~ref_small_vector() {
|
||||
for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
|
||||
Py_XDECREF(m_ptrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable copy (prevent accidental double-decref)
|
||||
ref_small_vector(const ref_small_vector &) = delete;
|
||||
ref_small_vector &operator=(const ref_small_vector &) = delete;
|
||||
|
||||
// Move is allowed
|
||||
ref_small_vector(ref_small_vector &&other) noexcept : m_ptrs(std::move(other.m_ptrs)) {
|
||||
// other.m_ptrs is now empty, so its destructor won't decref anything
|
||||
}
|
||||
|
||||
ref_small_vector &operator=(ref_small_vector &&other) noexcept {
|
||||
if (this != &other) {
|
||||
// Decref our current contents
|
||||
for (std::size_t i = 0; i < m_ptrs.size(); ++i) {
|
||||
Py_XDECREF(m_ptrs[i]);
|
||||
}
|
||||
m_ptrs = std::move(other.m_ptrs);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Add a pointer, taking ownership (no incref, will decref on destruction)
|
||||
void push_back_steal(PyObject *p) { m_ptrs.push_back(p); }
|
||||
|
||||
/// Add a pointer, borrowing (increfs now, will decref on destruction)
|
||||
void push_back_borrow(PyObject *p) {
|
||||
Py_XINCREF(p);
|
||||
m_ptrs.push_back(p);
|
||||
}
|
||||
|
||||
/// Add a null pointer (for PY_VECTORCALL_ARGUMENTS_OFFSET slot)
|
||||
void push_back_null() { m_ptrs.push_back(nullptr); }
|
||||
|
||||
void reserve(std::size_t sz) { m_ptrs.reserve(sz); }
|
||||
|
||||
std::size_t size() const { return m_ptrs.size(); }
|
||||
|
||||
PyObject *operator[](std::size_t idx) const { return m_ptrs[idx]; }
|
||||
|
||||
PyObject *const *data() const { return m_ptrs.data(); }
|
||||
|
||||
private:
|
||||
small_vector<PyObject *, InlineSize> m_ptrs;
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
||||
@@ -71,7 +71,7 @@ inline PyTypeObject *make_static_property_type() {
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
|
||||
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(PyType_Type.tp_alloc(&PyType_Type, 0));
|
||||
if (!heap_type) {
|
||||
pybind11_fail("make_static_property_type(): error allocating type!");
|
||||
}
|
||||
@@ -98,7 +98,7 @@ inline PyTypeObject *make_static_property_type() {
|
||||
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
|
||||
}
|
||||
|
||||
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
|
||||
setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
|
||||
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
|
||||
|
||||
return type;
|
||||
@@ -207,7 +207,7 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
|
||||
|
||||
/// Cleanup the type-info for a pybind11-registered type.
|
||||
extern "C" inline void pybind11_meta_dealloc(PyObject *obj) {
|
||||
with_internals([obj](internals &internals) {
|
||||
with_internals_if_internals([obj](internals &internals) {
|
||||
auto *type = (PyTypeObject *) obj;
|
||||
|
||||
// A pybind11-registered type will:
|
||||
@@ -265,7 +265,7 @@ inline PyTypeObject *make_default_metaclass() {
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto *heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
|
||||
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(PyType_Type.tp_alloc(&PyType_Type, 0));
|
||||
if (!heap_type) {
|
||||
pybind11_fail("make_default_metaclass(): error allocating metaclass!");
|
||||
}
|
||||
@@ -291,7 +291,7 @@ inline PyTypeObject *make_default_metaclass() {
|
||||
pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!");
|
||||
}
|
||||
|
||||
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
|
||||
setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
|
||||
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
|
||||
|
||||
return type;
|
||||
@@ -306,7 +306,7 @@ inline void traverse_offset_bases(void *valueptr,
|
||||
instance *self,
|
||||
bool (*f)(void * /*parentptr*/, instance * /*self*/)) {
|
||||
for (handle h : reinterpret_borrow<tuple>(tinfo->type->tp_bases)) {
|
||||
if (auto *parent_tinfo = get_type_info((PyTypeObject *) h.ptr())) {
|
||||
if (auto *parent_tinfo = get_type_info(reinterpret_cast<PyTypeObject *>(h.ptr()))) {
|
||||
for (auto &c : parent_tinfo->implicit_casts) {
|
||||
if (c.first == tinfo->cpptype) {
|
||||
auto *parentptr = c.second(valueptr);
|
||||
@@ -530,7 +530,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
|
||||
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(metaclass->tp_alloc(metaclass, 0));
|
||||
if (!heap_type) {
|
||||
pybind11_fail("make_object_base_type(): error allocating type!");
|
||||
}
|
||||
@@ -557,11 +557,11 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
|
||||
pybind11_fail("PyType_Ready failed in make_object_base_type(): " + error_string());
|
||||
}
|
||||
|
||||
setattr((PyObject *) type, "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
|
||||
setattr(reinterpret_cast<PyObject *>(type), "__module__", str(PYBIND11_DUMMY_MODULE_NAME));
|
||||
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
|
||||
|
||||
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
|
||||
return (PyObject *) heap_type;
|
||||
return reinterpret_cast<PyObject *>(heap_type);
|
||||
}
|
||||
|
||||
/// dynamic_attr: Allow the garbage collector to traverse the internal instance `__dict__`.
|
||||
@@ -746,7 +746,7 @@ inline PyObject *make_new_python_type(const type_record &rec) {
|
||||
/* Allocate memory for docstring (Python will free this later on) */
|
||||
size_t size = std::strlen(rec.doc) + 1;
|
||||
#if PY_VERSION_HEX >= 0x030D0000
|
||||
tp_doc = (char *) PyMem_MALLOC(size);
|
||||
tp_doc = static_cast<char *>(PyMem_MALLOC(size));
|
||||
#else
|
||||
tp_doc = (char *) PyObject_MALLOC(size);
|
||||
#endif
|
||||
@@ -761,10 +761,10 @@ inline PyObject *make_new_python_type(const type_record &rec) {
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto *metaclass
|
||||
= rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr() : internals.default_metaclass;
|
||||
auto *metaclass = rec.metaclass.ptr() ? reinterpret_cast<PyTypeObject *>(rec.metaclass.ptr())
|
||||
: internals.default_metaclass;
|
||||
|
||||
auto *heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
|
||||
auto *heap_type = reinterpret_cast<PyHeapTypeObject *>(metaclass->tp_alloc(metaclass, 0));
|
||||
if (!heap_type) {
|
||||
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
|
||||
}
|
||||
@@ -777,7 +777,7 @@ inline PyObject *make_new_python_type(const type_record &rec) {
|
||||
auto *type = &heap_type->ht_type;
|
||||
type->tp_name = full_name;
|
||||
type->tp_doc = tp_doc;
|
||||
type->tp_base = type_incref((PyTypeObject *) base);
|
||||
type->tp_base = type_incref(reinterpret_cast<PyTypeObject *>(base));
|
||||
type->tp_basicsize = static_cast<ssize_t>(sizeof(instance));
|
||||
if (!bases.empty()) {
|
||||
type->tp_bases = bases.release().ptr();
|
||||
@@ -818,18 +818,18 @@ inline PyObject *make_new_python_type(const type_record &rec) {
|
||||
|
||||
/* Register type with the parent scope */
|
||||
if (rec.scope) {
|
||||
setattr(rec.scope, rec.name, (PyObject *) type);
|
||||
setattr(rec.scope, rec.name, reinterpret_cast<PyObject *>(type));
|
||||
} else {
|
||||
Py_INCREF(type); // Keep it alive forever (reference leak)
|
||||
}
|
||||
|
||||
if (module_) { // Needed by pydoc
|
||||
setattr((PyObject *) type, "__module__", module_);
|
||||
setattr(reinterpret_cast<PyObject *>(type), "__module__", module_);
|
||||
}
|
||||
|
||||
PYBIND11_SET_OLDPY_QUALNAME(type, qualname);
|
||||
|
||||
return (PyObject *) type;
|
||||
return reinterpret_cast<PyObject *>(type);
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1914))
|
||||
#if defined(__cpp_lib_launder) && !(defined(_MSC_VER) && (_MSC_VER < 1920)) // See PR #5968
|
||||
# define PYBIND11_STD_LAUNDER std::launder
|
||||
# define PYBIND11_HAS_STD_LAUNDER 1
|
||||
#else
|
||||
@@ -441,12 +441,11 @@ Note that this is run once for each (sub-)interpreter the module is imported int
|
||||
possibly concurrently. The PyModuleDef is allowed to be static, but the PyObject* resulting from
|
||||
PyModuleDef_Init should be treated like any other PyObject (so not shared across interpreters).
|
||||
*/
|
||||
#define PYBIND11_MODULE_PYINIT(name, pre_init, ...) \
|
||||
#define PYBIND11_MODULE_PYINIT(name, ...) \
|
||||
static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \
|
||||
PYBIND11_PLUGIN_IMPL(name) { \
|
||||
PYBIND11_CHECK_PYTHON_VERSION \
|
||||
pre_init; \
|
||||
PYBIND11_ENSURE_INTERNALS_READY \
|
||||
pybind11::detail::ensure_internals(); \
|
||||
static ::pybind11::detail::slots_array mod_def_slots = ::pybind11::detail::init_slots( \
|
||||
&PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \
|
||||
static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \
|
||||
@@ -465,6 +464,7 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across
|
||||
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
|
||||
int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject * pm) { \
|
||||
try { \
|
||||
pybind11::detail::ensure_internals(); \
|
||||
auto m = pybind11::reinterpret_borrow<::pybind11::module_>(pm); \
|
||||
if (!pybind11::detail::get_cached_module(m.attr("__spec__").attr("name"))) { \
|
||||
PYBIND11_CONCAT(pybind11_init_, name)(m); \
|
||||
@@ -518,8 +518,7 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across
|
||||
|
||||
\endrst */
|
||||
#define PYBIND11_MODULE(name, variable, ...) \
|
||||
PYBIND11_MODULE_PYINIT( \
|
||||
name, (pybind11::detail::get_num_interpreters_seen() += 1), ##__VA_ARGS__) \
|
||||
PYBIND11_MODULE_PYINIT(name, ##__VA_ARGS__) \
|
||||
PYBIND11_MODULE_EXEC(name, variable)
|
||||
|
||||
// pop gnu-zero-variadic-macro-arguments
|
||||
@@ -590,14 +589,10 @@ enum class return_value_policy : uint8_t {
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
inline static constexpr int log2(size_t n, int k = 0) {
|
||||
return (n <= 1) ? k : log2(n >> 1, k + 1);
|
||||
}
|
||||
static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : log2(n >> 1, k + 1); }
|
||||
|
||||
// Returns the size as a multiple of sizeof(void *), rounded up.
|
||||
inline static constexpr size_t size_in_ptrs(size_t s) {
|
||||
return 1 + ((s - 1) >> log2(sizeof(void *)));
|
||||
}
|
||||
static constexpr size_t size_in_ptrs(size_t s) { return 1 + ((s - 1) >> log2(sizeof(void *))); }
|
||||
|
||||
/**
|
||||
* The space to allocate for simple layout instance holders (see below) in multiple of the size of
|
||||
|
||||
@@ -21,13 +21,13 @@ inline bool type_is_managed_by_our_internals(PyTypeObject *type_obj) {
|
||||
return bool(internals.registered_types_py.find(type_obj)
|
||||
!= internals.registered_types_py.end());
|
||||
#else
|
||||
return bool(type_obj->tp_new == pybind11_object_new);
|
||||
return (type_obj->tp_new == pybind11_object_new);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool is_instance_method_of_type(PyTypeObject *type_obj, PyObject *attr_name) {
|
||||
PyObject *descr = _PyType_Lookup(type_obj, attr_name);
|
||||
return bool((descr != nullptr) && PyInstanceMethod_Check(descr));
|
||||
return ((descr != nullptr) && PyInstanceMethod_Check(descr));
|
||||
}
|
||||
|
||||
inline object try_get_cpp_conduit_method(PyObject *obj) {
|
||||
|
||||
@@ -126,7 +126,7 @@ inline bool is_function_record_PyObject(PyObject *obj) {
|
||||
|
||||
inline function_record *function_record_ptr_from_PyObject(PyObject *obj) {
|
||||
if (is_function_record_PyObject(obj)) {
|
||||
return ((detail::function_record_PyObject *) obj)->cpp_func_rec;
|
||||
return (reinterpret_cast<detail::function_record_PyObject *>(obj))->cpp_func_rec;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -137,7 +137,7 @@ inline object function_record_PyObject_New() {
|
||||
throw error_already_set();
|
||||
}
|
||||
py_func_rec->cpp_func_rec = nullptr; // For clarity/purity. Redundant in practice.
|
||||
return reinterpret_steal<object>((PyObject *) py_func_rec);
|
||||
return reinterpret_steal<object>(reinterpret_cast<PyObject *>(py_func_rec));
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)
|
||||
|
||||
@@ -476,9 +476,9 @@ void setstate(value_and_holder &v_h, std::pair<T, O> &&result, bool need_alias)
|
||||
return;
|
||||
}
|
||||
// Our tests never run into an unset dict, but being careful here for now (see #5658)
|
||||
auto dict = getattr((PyObject *) v_h.inst, "__dict__", none());
|
||||
auto dict = getattr(reinterpret_cast<PyObject *>(v_h.inst), "__dict__", none());
|
||||
if (dict.is_none()) {
|
||||
setattr((PyObject *) v_h.inst, "__dict__", d);
|
||||
setattr(reinterpret_cast<PyObject *>(v_h.inst), "__dict__", d);
|
||||
} else {
|
||||
// Keep the original object dict and just update it
|
||||
if (PyDict_Update(dict.ptr(), d.ptr()) < 0) {
|
||||
|
||||
@@ -103,7 +103,7 @@ public:
|
||||
// However, in GraalPy (as of v24.2 or older), TSS is implemented by Java and this call
|
||||
// requires a living Python interpreter.
|
||||
#ifdef GRAALVM_PYTHON
|
||||
if (!Py_IsInitialized() || _Py_IsFinalizing()) {
|
||||
if (Py_IsInitialized() == 0 || _Py_IsFinalizing() != 0) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
@@ -143,6 +143,38 @@ inline PyTypeObject *make_default_metaclass();
|
||||
inline PyObject *make_object_base_type(PyTypeObject *metaclass);
|
||||
inline void translate_exception(std::exception_ptr p);
|
||||
|
||||
inline PyThreadState *get_thread_state_unchecked() {
|
||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
||||
return PyThreadState_GET();
|
||||
#elif PY_VERSION_HEX < 0x030D0000
|
||||
return _PyThreadState_UncheckedGet();
|
||||
#else
|
||||
return PyThreadState_GetUnchecked();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline PyInterpreterState *get_interpreter_state_unchecked() {
|
||||
auto *tstate = get_thread_state_unchecked();
|
||||
return tstate ? tstate->interp : nullptr;
|
||||
}
|
||||
|
||||
inline object get_python_state_dict() {
|
||||
object state_dict;
|
||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
||||
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
|
||||
#else
|
||||
auto *istate = get_interpreter_state_unchecked();
|
||||
if (istate) {
|
||||
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
|
||||
}
|
||||
#endif
|
||||
if (!state_dict) {
|
||||
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
|
||||
throw error_already_set();
|
||||
}
|
||||
return state_dict;
|
||||
}
|
||||
|
||||
// Python loads modules by default with dlopen with the RTLD_LOCAL flag; under libc++ and possibly
|
||||
// other STLs, this means `typeid(A)` from one module won't equal `typeid(A)` from another module
|
||||
// even when `A` is the same, non-hidden-visibility type (e.g. from a common include). Under
|
||||
@@ -186,7 +218,7 @@ template <typename value_type>
|
||||
using type_map = std::unordered_map<std::type_index, value_type, type_hash, type_equal_to>;
|
||||
|
||||
struct override_hash {
|
||||
inline size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
|
||||
size_t operator()(const std::pair<const PyObject *, const char *> &v) const {
|
||||
size_t value = std::hash<const void *>()(v.first);
|
||||
value ^= std::hash<const void *>()(v.second) + 0x9e3779b9 + (value << 6) + (value >> 2);
|
||||
return value;
|
||||
@@ -285,11 +317,8 @@ struct internals {
|
||||
|
||||
internals()
|
||||
: static_property_type(make_static_property_type()),
|
||||
default_metaclass(make_default_metaclass()) {
|
||||
default_metaclass(make_default_metaclass()), istate(get_interpreter_state_unchecked()) {
|
||||
tstate.set(nullptr); // See PR #5870
|
||||
PyThreadState *cur_tstate = PyThreadState_Get();
|
||||
|
||||
istate = cur_tstate->interp;
|
||||
registered_exception_translators.push_front(&translate_exception);
|
||||
#ifdef Py_GIL_DISABLED
|
||||
// Scale proportional to the number of cores. 2x is a heuristic to reduce contention.
|
||||
@@ -308,7 +337,19 @@ struct internals {
|
||||
internals(internals &&other) = delete;
|
||||
internals &operator=(const internals &other) = delete;
|
||||
internals &operator=(internals &&other) = delete;
|
||||
~internals() = default;
|
||||
~internals() {
|
||||
// Normally this destructor runs during interpreter finalization and it may DECREF things.
|
||||
// In odd finalization scenarios it might end up running after the interpreter has
|
||||
// completely shut down, In that case, we should not decref these objects because pymalloc
|
||||
// is gone. This also applies across sub-interpreters, we should only DECREF when the
|
||||
// original owning interpreter is active.
|
||||
auto *cur_istate = get_interpreter_state_unchecked();
|
||||
if (cur_istate && cur_istate == istate) {
|
||||
Py_CLEAR(instance_base);
|
||||
Py_CLEAR(default_metaclass);
|
||||
Py_CLEAR(static_property_type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// the internals struct (above) is shared between all the modules. local_internals are only
|
||||
@@ -318,6 +359,8 @@ struct internals {
|
||||
// impact any other modules, because the only things accessing the local internals is the
|
||||
// module that contains them.
|
||||
struct local_internals {
|
||||
local_internals() : istate(get_interpreter_state_unchecked()) {}
|
||||
|
||||
// It should be safe to use fast_type_map here because this entire
|
||||
// data structure is scoped to our single module, and thus a single
|
||||
// DSO and single instance of type_info for any particular type.
|
||||
@@ -325,6 +368,19 @@ struct local_internals {
|
||||
|
||||
std::forward_list<ExceptionTranslator> registered_exception_translators;
|
||||
PyTypeObject *function_record_py_type = nullptr;
|
||||
PyInterpreterState *istate = nullptr;
|
||||
|
||||
~local_internals() {
|
||||
// Normally this destructor runs during interpreter finalization and it may DECREF things.
|
||||
// In odd finalization scenarios it might end up running after the interpreter has
|
||||
// completely shut down, In that case, we should not decref these objects because pymalloc
|
||||
// is gone. This also applies across sub-interpreters, we should only DECREF when the
|
||||
// original owning interpreter is active.
|
||||
auto *cur_istate = get_interpreter_state_unchecked();
|
||||
if (cur_istate && cur_istate == istate) {
|
||||
Py_CLEAR(function_record_py_type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum class holder_enum_t : uint8_t {
|
||||
@@ -408,21 +464,12 @@ struct native_enum_record {
|
||||
"__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
|
||||
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
|
||||
|
||||
inline PyThreadState *get_thread_state_unchecked() {
|
||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
||||
return PyThreadState_GET();
|
||||
#elif PY_VERSION_HEX < 0x030D0000
|
||||
return _PyThreadState_UncheckedGet();
|
||||
#else
|
||||
return PyThreadState_GetUnchecked();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// We use this counter to figure out if there are or have been multiple subinterpreters active at
|
||||
/// any point. This must never decrease while any interpreter may be running in any thread!
|
||||
inline std::atomic<int> &get_num_interpreters_seen() {
|
||||
static std::atomic<int> counter(0);
|
||||
return counter;
|
||||
/// We use this to figure out if there are or have been multiple subinterpreters active at any
|
||||
/// point. This must never go from true to false while any interpreter may be running in any
|
||||
/// thread!
|
||||
inline std::atomic_bool &has_seen_non_main_interpreter() {
|
||||
static std::atomic_bool multi(false);
|
||||
return multi;
|
||||
}
|
||||
|
||||
template <class T,
|
||||
@@ -529,25 +576,85 @@ inline void translate_local_exception(std::exception_ptr p) {
|
||||
}
|
||||
#endif
|
||||
|
||||
inline object get_python_state_dict() {
|
||||
object state_dict;
|
||||
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
|
||||
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
|
||||
#else
|
||||
# if PY_VERSION_HEX < 0x03090000
|
||||
PyInterpreterState *istate = _PyInterpreterState_Get();
|
||||
# else
|
||||
PyInterpreterState *istate = PyInterpreterState_Get();
|
||||
# endif
|
||||
if (istate) {
|
||||
state_dict = reinterpret_borrow<object>(PyInterpreterState_GetDict(istate));
|
||||
// Get or create per-storage capsule in the current interpreter's state dict.
|
||||
// - The storage is interpreter-dependent: different interpreters will have different storage.
|
||||
// This is important when using multiple-interpreters, to avoid sharing unshareable objects
|
||||
// between interpreters.
|
||||
// - There is one storage per `key` in an interpreter and it is accessible between all extensions
|
||||
// in the same interpreter.
|
||||
// - The life span of the storage is tied to the interpreter: it will be kept alive until the
|
||||
// interpreter shuts down.
|
||||
//
|
||||
// Use test-and-set pattern with `PyDict_SetDefault` for thread-safe concurrent access.
|
||||
// WARNING: There can be multiple threads creating the storage at the same time, while only one
|
||||
// will succeed in inserting its capsule into the dict. Therefore, the deleter will be
|
||||
// used to clean up the storage of the unused capsules.
|
||||
//
|
||||
// Returns: pair of (pointer to storage, bool indicating if newly created).
|
||||
// The bool follows std::map::insert convention: true = created, false = existed.
|
||||
template <typename Payload>
|
||||
std::pair<Payload *, bool> atomic_get_or_create_in_state_dict(const char *key,
|
||||
void (*dtor)(PyObject *) = nullptr) {
|
||||
error_scope err_scope; // preserve any existing Python error states
|
||||
|
||||
auto state_dict = reinterpret_borrow<dict>(get_python_state_dict());
|
||||
PyObject *capsule_obj = nullptr;
|
||||
bool created = false;
|
||||
|
||||
// Try to get existing storage (fast path).
|
||||
capsule_obj = dict_getitemstring(state_dict.ptr(), key);
|
||||
if (capsule_obj == nullptr) {
|
||||
if (PyErr_Occurred()) {
|
||||
throw error_already_set();
|
||||
}
|
||||
// Storage doesn't exist yet, create a new one.
|
||||
// Use unique_ptr for exception safety: if capsule creation throws, the storage is
|
||||
// automatically deleted.
|
||||
auto storage_ptr = std::unique_ptr<Payload>(new Payload{});
|
||||
auto new_capsule
|
||||
= capsule(storage_ptr.get(),
|
||||
// The destructor will be called when the capsule is GC'ed.
|
||||
// If the insert below fails (entry already in the dict), then this
|
||||
// destructor will be called on the newly created capsule at the end of this
|
||||
// function, and we want to just release this memory.
|
||||
/*destructor=*/[](void *v) { delete static_cast<Payload *>(v); });
|
||||
// At this point, the capsule object is created successfully.
|
||||
// Release the unique_ptr and let the capsule object own the storage to avoid double-free.
|
||||
(void) storage_ptr.release();
|
||||
|
||||
// Use `PyDict_SetDefault` for atomic test-and-set:
|
||||
// - If key doesn't exist, inserts our capsule and returns it.
|
||||
// - If key exists (another thread inserted first), returns the existing value.
|
||||
// This is thread-safe because `PyDict_SetDefault` will hold a lock on the dict.
|
||||
//
|
||||
// NOTE: Here we use `PyDict_SetDefault` instead of `PyDict_SetDefaultRef` because the
|
||||
// capsule is kept alive until interpreter shutdown, so we do not need to handle
|
||||
// incref and decref here.
|
||||
capsule_obj = dict_setdefaultstring(state_dict.ptr(), key, new_capsule.ptr());
|
||||
if (capsule_obj == nullptr) {
|
||||
throw error_already_set();
|
||||
}
|
||||
created = (capsule_obj == new_capsule.ptr());
|
||||
// - If key already existed, our `new_capsule` is not inserted, it will be destructed when
|
||||
// going out of scope here, and will call the destructor set above.
|
||||
// - Otherwise, our `new_capsule` is now in the dict, and it owns the storage and the state
|
||||
// dict will incref it. We need to set the caller's destructor on it, which will be
|
||||
// called when the interpreter shuts down.
|
||||
if (created && dtor) {
|
||||
if (PyCapsule_SetDestructor(capsule_obj, dtor) < 0) {
|
||||
throw error_already_set();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!state_dict) {
|
||||
raise_from(PyExc_SystemError, "pybind11::detail::get_python_state_dict() FAILED");
|
||||
|
||||
// Get the storage pointer from the capsule.
|
||||
void *raw_ptr = PyCapsule_GetPointer(capsule_obj, /*name=*/nullptr);
|
||||
if (!raw_ptr) {
|
||||
raise_from(PyExc_SystemError,
|
||||
"pybind11::detail::atomic_get_or_create_in_state_dict() FAILED");
|
||||
throw error_already_set();
|
||||
}
|
||||
return state_dict;
|
||||
return std::pair<Payload *, bool>(static_cast<Payload *>(raw_ptr), created);
|
||||
}
|
||||
|
||||
template <typename InternalsType>
|
||||
@@ -555,7 +662,7 @@ class internals_pp_manager {
|
||||
public:
|
||||
using on_fetch_function = void(InternalsType *);
|
||||
|
||||
inline static internals_pp_manager &get_instance(char const *id, on_fetch_function *on_fetch) {
|
||||
static internals_pp_manager &get_instance(char const *id, on_fetch_function *on_fetch) {
|
||||
static internals_pp_manager instance(id, on_fetch);
|
||||
return instance;
|
||||
}
|
||||
@@ -564,7 +671,7 @@ public:
|
||||
/// acquire the GIL. Will never return nullptr.
|
||||
std::unique_ptr<InternalsType> *get_pp() {
|
||||
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
if (get_num_interpreters_seen() > 1) {
|
||||
if (has_seen_non_main_interpreter()) {
|
||||
// Whenever the interpreter changes on the current thread we need to invalidate the
|
||||
// internals_pp so that it can be pulled from the interpreter's state dict. That is
|
||||
// slow, so we use the current PyThreadState to check if it is necessary.
|
||||
@@ -590,7 +697,7 @@ public:
|
||||
/// Drop all the references we're currently holding.
|
||||
void unref() {
|
||||
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
if (get_num_interpreters_seen() > 1) {
|
||||
if (has_seen_non_main_interpreter()) {
|
||||
last_istate_tls() = nullptr;
|
||||
internals_p_tls() = nullptr;
|
||||
return;
|
||||
@@ -601,14 +708,13 @@ public:
|
||||
|
||||
void destroy() {
|
||||
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
if (get_num_interpreters_seen() > 1) {
|
||||
if (has_seen_non_main_interpreter()) {
|
||||
auto *tstate = get_thread_state_unchecked();
|
||||
// this could be called without an active interpreter, just use what was cached
|
||||
if (!tstate || tstate->interp == last_istate_tls()) {
|
||||
auto tpp = internals_p_tls();
|
||||
if (tpp) {
|
||||
delete tpp;
|
||||
}
|
||||
|
||||
delete tpp;
|
||||
}
|
||||
unref();
|
||||
return;
|
||||
@@ -622,27 +728,32 @@ private:
|
||||
internals_pp_manager(char const *id, on_fetch_function *on_fetch)
|
||||
: holder_id_(id), on_fetch_(on_fetch) {}
|
||||
|
||||
static void internals_shutdown(PyObject *capsule) {
|
||||
auto *pp = static_cast<std::unique_ptr<InternalsType> *>(
|
||||
PyCapsule_GetPointer(capsule, nullptr));
|
||||
if (pp) {
|
||||
pp->reset();
|
||||
}
|
||||
// We reset the unique_ptr's contents but cannot delete the unique_ptr itself here.
|
||||
// The pp_manager in this module (and possibly other modules sharing internals) holds
|
||||
// a raw pointer to this unique_ptr, and that pointer would dangle if we deleted it now.
|
||||
//
|
||||
// For pybind11-owned interpreters (via embed.h or subinterpreter.h), destroy() is
|
||||
// called after Py_Finalize/Py_EndInterpreter completes, which safely deletes the
|
||||
// unique_ptr. For interpreters not owned by pybind11 (e.g., a pybind11 extension
|
||||
// loaded into an external interpreter), destroy() is never called and the unique_ptr
|
||||
// shell (8 bytes, not its contents) is leaked.
|
||||
// (See PR #5958 for ideas to eliminate this leak.)
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalsType> *get_or_create_pp_in_state_dict() {
|
||||
error_scope err_scope;
|
||||
dict state_dict = get_python_state_dict();
|
||||
auto internals_obj
|
||||
= reinterpret_steal<object>(dict_getitemstringref(state_dict.ptr(), holder_id_));
|
||||
std::unique_ptr<InternalsType> *pp = nullptr;
|
||||
if (internals_obj) {
|
||||
void *raw_ptr = PyCapsule_GetPointer(internals_obj.ptr(), /*name=*/nullptr);
|
||||
if (!raw_ptr) {
|
||||
raise_from(PyExc_SystemError,
|
||||
"pybind11::detail::internals_pp_manager::get_pp_from_dict() FAILED");
|
||||
throw error_already_set();
|
||||
}
|
||||
pp = reinterpret_cast<std::unique_ptr<InternalsType> *>(raw_ptr);
|
||||
if (on_fetch_ && pp) {
|
||||
on_fetch_(pp->get());
|
||||
}
|
||||
} else {
|
||||
pp = new std::unique_ptr<InternalsType>;
|
||||
// NOLINTNEXTLINE(bugprone-casting-through-void)
|
||||
state_dict[holder_id_] = capsule(reinterpret_cast<void *>(pp));
|
||||
auto result = atomic_get_or_create_in_state_dict<std::unique_ptr<InternalsType>>(
|
||||
holder_id_, &internals_shutdown);
|
||||
auto *pp = result.first;
|
||||
bool created = result.second;
|
||||
// Only call on_fetch_ when fetching existing internals, not when creating new ones.
|
||||
if (!created && on_fetch_ && pp) {
|
||||
on_fetch_(pp->get());
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
@@ -661,6 +772,8 @@ private:
|
||||
|
||||
char const *holder_id_ = nullptr;
|
||||
on_fetch_function *on_fetch_ = nullptr;
|
||||
// Pointer-to-pointer to the singleton internals for the first seen interpreter (may not be the
|
||||
// main interpreter)
|
||||
std::unique_ptr<InternalsType> *internals_singleton_pp_;
|
||||
};
|
||||
|
||||
@@ -713,6 +826,16 @@ PYBIND11_NOINLINE internals &get_internals() {
|
||||
return *internals_ptr;
|
||||
}
|
||||
|
||||
inline void ensure_internals() {
|
||||
pybind11::detail::get_internals_pp_manager().unref();
|
||||
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
if (PyInterpreterState_Get() != PyInterpreterState_Main()) {
|
||||
has_seen_non_main_interpreter() = true;
|
||||
}
|
||||
#endif
|
||||
pybind11::detail::get_internals();
|
||||
}
|
||||
|
||||
inline internals_pp_manager<local_internals> &get_local_internals_pp_manager() {
|
||||
// Use the address of this static itself as part of the key, so that the value is uniquely tied
|
||||
// to where the module is loaded in memory
|
||||
@@ -745,6 +868,17 @@ inline auto with_internals(const F &cb) -> decltype(cb(get_internals())) {
|
||||
return cb(internals);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
inline void with_internals_if_internals(const F &cb) {
|
||||
auto &ppmgr = get_internals_pp_manager();
|
||||
auto &internals_ptr = *ppmgr.get_pp();
|
||||
if (internals_ptr) {
|
||||
auto &internals = *internals_ptr;
|
||||
PYBIND11_LOCK_INTERNALS(internals);
|
||||
cb(internals);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
inline auto with_exception_translators(const F &cb)
|
||||
-> decltype(cb(get_internals().registered_exception_translators,
|
||||
|
||||
@@ -125,7 +125,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
|
||||
assert(bases.empty());
|
||||
std::vector<PyTypeObject *> check;
|
||||
for (handle parent : reinterpret_borrow<tuple>(t->tp_bases)) {
|
||||
check.push_back((PyTypeObject *) parent.ptr());
|
||||
check.push_back(reinterpret_cast<PyTypeObject *>(parent.ptr()));
|
||||
}
|
||||
auto const &type_dict = get_internals().registered_types_py;
|
||||
for (size_t i = 0; i < check.size(); i++) {
|
||||
@@ -168,7 +168,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector<type_
|
||||
i--;
|
||||
}
|
||||
for (handle parent : reinterpret_borrow<tuple>(type->tp_bases)) {
|
||||
check.push_back((PyTypeObject *) parent.ptr());
|
||||
check.push_back(reinterpret_cast<PyTypeObject *>(parent.ptr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,7 +286,7 @@ PYBIND11_NOINLINE detail::type_info *get_type_info(const std::type_info &tp,
|
||||
|
||||
PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if_missing) {
|
||||
detail::type_info *type_info = get_type_info(tp, throw_if_missing);
|
||||
return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
|
||||
return handle(type_info ? (reinterpret_cast<PyObject *>(type_info->type)) : nullptr);
|
||||
}
|
||||
|
||||
inline bool try_incref(PyObject *obj) {
|
||||
@@ -506,7 +506,7 @@ PYBIND11_NOINLINE void instance::allocate_layout() {
|
||||
// efficient for small allocations like the one we're doing here;
|
||||
// for larger allocations they are just wrappers around malloc.
|
||||
// TODO: is this still true for pure Python 3.6?
|
||||
nonsimple.values_and_holders = (void **) PyMem_Calloc(space, sizeof(void *));
|
||||
nonsimple.values_and_holders = static_cast<void **>(PyMem_Calloc(space, sizeof(void *)));
|
||||
if (!nonsimple.values_and_holders) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
@@ -537,7 +537,7 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
for (const auto &vh : values_and_holders(it->second)) {
|
||||
if (vh.type == type) {
|
||||
return handle((PyObject *) it->second);
|
||||
return handle(reinterpret_cast<PyObject *>(it->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1700,7 +1700,7 @@ inline std::string quote_cpp_type_name(const std::string &cpp_type_name) {
|
||||
|
||||
PYBIND11_NOINLINE std::string type_info_description(const std::type_info &ti) {
|
||||
if (auto *type_data = get_type_info(ti)) {
|
||||
handle th((PyObject *) type_data->type);
|
||||
handle th(reinterpret_cast<PyObject *>(type_data->type));
|
||||
return th.attr("__module__").cast<std::string>() + '.'
|
||||
+ th.attr("__qualname__").cast<std::string>();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@ struct value_and_holder {
|
||||
} else if (v) {
|
||||
inst->nonsimple.status[index] |= instance::status_holder_constructed;
|
||||
} else {
|
||||
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_holder_constructed;
|
||||
inst->nonsimple.status[index]
|
||||
&= static_cast<std::uint8_t>(~instance::status_holder_constructed);
|
||||
}
|
||||
}
|
||||
bool instance_registered() const {
|
||||
@@ -69,7 +70,8 @@ struct value_and_holder {
|
||||
} else if (v) {
|
||||
inst->nonsimple.status[index] |= instance::status_instance_registered;
|
||||
} else {
|
||||
inst->nonsimple.status[index] &= (std::uint8_t) ~instance::status_instance_registered;
|
||||
inst->nonsimple.status[index]
|
||||
&= static_cast<std::uint8_t>(~instance::status_instance_registered);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
PYBIND11_WARNING_PUSH
|
||||
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
|
||||
#define PYBIND11_EMBEDDED_MODULE(name, variable, ...) \
|
||||
PYBIND11_MODULE_PYINIT(name, {}, ##__VA_ARGS__) \
|
||||
PYBIND11_MODULE_PYINIT(name, ##__VA_ARGS__) \
|
||||
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)( \
|
||||
PYBIND11_TOSTRING(name), PYBIND11_CONCAT(PyInit_, name)); \
|
||||
PYBIND11_MODULE_EXEC(name, variable)
|
||||
@@ -202,7 +202,7 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
|
||||
#endif
|
||||
|
||||
// There is exactly one interpreter alive currently.
|
||||
detail::get_num_interpreters_seen() = 1;
|
||||
detail::has_seen_non_main_interpreter() = false;
|
||||
}
|
||||
|
||||
/** \rst
|
||||
@@ -242,12 +242,12 @@ inline void initialize_interpreter(bool init_signal_handlers = true,
|
||||
\endrst */
|
||||
inline void finalize_interpreter() {
|
||||
// get rid of any thread-local interpreter cache that currently exists
|
||||
if (detail::get_num_interpreters_seen() > 1) {
|
||||
if (detail::has_seen_non_main_interpreter()) {
|
||||
detail::get_internals_pp_manager().unref();
|
||||
detail::get_local_internals_pp_manager().unref();
|
||||
|
||||
// We know there can be no other interpreter alive now, so we can lower the count
|
||||
detail::get_num_interpreters_seen() = 1;
|
||||
// We know there can be no other interpreter alive now
|
||||
detail::has_seen_non_main_interpreter() = false;
|
||||
}
|
||||
|
||||
// Re-fetch the internals pointer-to-pointer (but not the internals itself, which might not
|
||||
@@ -265,8 +265,8 @@ inline void finalize_interpreter() {
|
||||
// avoid undefined behaviors when initializing another interpreter
|
||||
detail::get_local_internals_pp_manager().destroy();
|
||||
|
||||
// We know there is no interpreter alive now, so we can reset the count
|
||||
detail::get_num_interpreters_seen() = 0;
|
||||
// We know there is no interpreter alive now, so we can reset the multi-flag
|
||||
detail::has_seen_non_main_interpreter() = false;
|
||||
}
|
||||
|
||||
/** \rst
|
||||
|
||||
@@ -3,17 +3,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "detail/common.h"
|
||||
#include "detail/internals.h"
|
||||
#include "gil.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
#if defined(Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
|
||||
# include <atomic>
|
||||
#endif
|
||||
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
# include <cstdint>
|
||||
# include <memory>
|
||||
# include <string>
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
#if defined(Py_GIL_DISABLED) || defined(PYBIND11_HAS_SUBINTERPRETER_SUPPORT)
|
||||
using atomic_bool = std::atomic_bool;
|
||||
#else
|
||||
using atomic_bool = bool;
|
||||
#endif
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
// Use the `gil_safe_call_once_and_store` class below instead of the naive
|
||||
//
|
||||
// static auto imported_obj = py::module_::import("module_name"); // BAD, DO NOT USE!
|
||||
@@ -48,12 +62,23 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
// functions, which is usually the case.
|
||||
//
|
||||
// For in-depth background, see docs/advanced/deadlock.md
|
||||
#ifndef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
// Subinterpreter support is disabled.
|
||||
// In this case, we can store the result globally, because there is only a single interpreter.
|
||||
//
|
||||
// The life span of the stored result is the entire process lifetime. It is leaked on process
|
||||
// termination to avoid destructor calls after the Python interpreter was finalized.
|
||||
template <typename T>
|
||||
class gil_safe_call_once_and_store {
|
||||
public:
|
||||
// PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
|
||||
//
|
||||
// NOTE: The second parameter (finalize callback) is intentionally unused when subinterpreter
|
||||
// support is disabled. In that case, storage is process-global and intentionally leaked to
|
||||
// avoid calling destructors after the Python interpreter has been finalized.
|
||||
template <typename Callable>
|
||||
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn) {
|
||||
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn,
|
||||
void (*)(T &) /*unused*/ = nullptr) {
|
||||
if (!is_initialized_) { // This read is guarded by the GIL.
|
||||
// Multiple threads may enter here, because the GIL is released in the next line and
|
||||
// CPython API calls in the `fn()` call below may release and reacquire the GIL.
|
||||
@@ -74,29 +99,175 @@ public:
|
||||
T &get_stored() {
|
||||
assert(is_initialized_);
|
||||
PYBIND11_WARNING_PUSH
|
||||
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
|
||||
# if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5
|
||||
// Needed for gcc 4.8.5
|
||||
PYBIND11_WARNING_DISABLE_GCC("-Wstrict-aliasing")
|
||||
#endif
|
||||
# endif
|
||||
return *reinterpret_cast<T *>(storage_);
|
||||
PYBIND11_WARNING_POP
|
||||
}
|
||||
|
||||
constexpr gil_safe_call_once_and_store() = default;
|
||||
// The instance is a global static, so its destructor runs when the process
|
||||
// is terminating. Therefore, do nothing here because the Python interpreter
|
||||
// may have been finalized already.
|
||||
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
|
||||
|
||||
// Disable copy and move operations.
|
||||
gil_safe_call_once_and_store(const gil_safe_call_once_and_store &) = delete;
|
||||
gil_safe_call_once_and_store(gil_safe_call_once_and_store &&) = delete;
|
||||
gil_safe_call_once_and_store &operator=(const gil_safe_call_once_and_store &) = delete;
|
||||
gil_safe_call_once_and_store &operator=(gil_safe_call_once_and_store &&) = delete;
|
||||
|
||||
private:
|
||||
// The global static storage (per-process) when subinterpreter support is disabled.
|
||||
alignas(T) char storage_[sizeof(T)] = {};
|
||||
std::once_flag once_flag_ = {};
|
||||
#ifdef Py_GIL_DISABLED
|
||||
std::atomic_bool
|
||||
#else
|
||||
bool
|
||||
#endif
|
||||
is_initialized_{false};
|
||||
std::once_flag once_flag_;
|
||||
|
||||
// The `is_initialized_`-`storage_` pair is very similar to `std::optional`,
|
||||
// but the latter does not have the triviality properties of former,
|
||||
// therefore `std::optional` is not a viable alternative here.
|
||||
detail::atomic_bool is_initialized_{false};
|
||||
};
|
||||
#else
|
||||
// Subinterpreter support is enabled.
|
||||
// In this case, we should store the result per-interpreter instead of globally, because each
|
||||
// subinterpreter has its own separate state. The cached result may not shareable across
|
||||
// interpreters (e.g., imported modules and their members).
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename T>
|
||||
struct call_once_storage {
|
||||
alignas(T) char storage[sizeof(T)] = {};
|
||||
std::once_flag once_flag;
|
||||
void (*finalize)(T &) = nullptr;
|
||||
std::atomic_bool is_initialized{false};
|
||||
|
||||
call_once_storage() = default;
|
||||
~call_once_storage() {
|
||||
if (is_initialized) {
|
||||
if (finalize != nullptr) {
|
||||
finalize(*reinterpret_cast<T *>(storage));
|
||||
} else {
|
||||
reinterpret_cast<T *>(storage)->~T();
|
||||
}
|
||||
}
|
||||
}
|
||||
call_once_storage(const call_once_storage &) = delete;
|
||||
call_once_storage(call_once_storage &&) = delete;
|
||||
call_once_storage &operator=(const call_once_storage &) = delete;
|
||||
call_once_storage &operator=(call_once_storage &&) = delete;
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
// Prefix for storage keys in the interpreter state dict.
|
||||
# define PYBIND11_CALL_ONCE_STORAGE_KEY_PREFIX PYBIND11_INTERNALS_ID "_call_once_storage__"
|
||||
|
||||
// The life span of the stored result is the entire interpreter lifetime. An additional
|
||||
// `finalize_fn` can be provided to clean up the stored result when the interpreter is destroyed.
|
||||
template <typename T>
|
||||
class gil_safe_call_once_and_store {
|
||||
public:
|
||||
// PRECONDITION: The GIL must be held when `call_once_and_store_result()` is called.
|
||||
template <typename Callable>
|
||||
gil_safe_call_once_and_store &call_once_and_store_result(Callable &&fn,
|
||||
void (*finalize_fn)(T &) = nullptr) {
|
||||
if (!is_last_storage_valid()) {
|
||||
// Multiple threads may enter here, because the GIL is released in the next line and
|
||||
// CPython API calls in the `fn()` call below may release and reacquire the GIL.
|
||||
gil_scoped_release gil_rel; // Needed to establish lock ordering.
|
||||
// There can be multiple threads going through here.
|
||||
storage_type *value = nullptr;
|
||||
{
|
||||
gil_scoped_acquire gil_acq; // Restore lock ordering.
|
||||
// This function is thread-safe under free-threading.
|
||||
value = get_or_create_storage_in_state_dict();
|
||||
}
|
||||
assert(value != nullptr);
|
||||
std::call_once(value->once_flag, [&] {
|
||||
// Only one thread will ever enter here.
|
||||
gil_scoped_acquire gil_acq;
|
||||
// fn may release, but will reacquire, the GIL.
|
||||
::new (value->storage) T(fn());
|
||||
value->finalize = finalize_fn;
|
||||
value->is_initialized = true;
|
||||
last_storage_ptr_ = reinterpret_cast<T *>(value->storage);
|
||||
is_initialized_by_at_least_one_interpreter_ = true;
|
||||
});
|
||||
// All threads will observe `is_initialized_by_at_least_one_interpreter_` as true here.
|
||||
}
|
||||
// Intentionally not returning `T &` to ensure the calling code is self-documenting.
|
||||
return *this;
|
||||
}
|
||||
|
||||
// This must only be called after `call_once_and_store_result()` was called.
|
||||
T &get_stored() {
|
||||
T *result = last_storage_ptr_;
|
||||
if (!is_last_storage_valid()) {
|
||||
gil_scoped_acquire gil_acq;
|
||||
auto *value = get_or_create_storage_in_state_dict();
|
||||
result = last_storage_ptr_ = reinterpret_cast<T *>(value->storage);
|
||||
}
|
||||
assert(result != nullptr);
|
||||
return *result;
|
||||
}
|
||||
|
||||
constexpr gil_safe_call_once_and_store() = default;
|
||||
// The instance is a global static, so its destructor runs when the process
|
||||
// is terminating. Therefore, do nothing here because the Python interpreter
|
||||
// may have been finalized already.
|
||||
PYBIND11_DTOR_CONSTEXPR ~gil_safe_call_once_and_store() = default;
|
||||
|
||||
// Disable copy and move operations because the memory address is used as key.
|
||||
gil_safe_call_once_and_store(const gil_safe_call_once_and_store &) = delete;
|
||||
gil_safe_call_once_and_store(gil_safe_call_once_and_store &&) = delete;
|
||||
gil_safe_call_once_and_store &operator=(const gil_safe_call_once_and_store &) = delete;
|
||||
gil_safe_call_once_and_store &operator=(gil_safe_call_once_and_store &&) = delete;
|
||||
|
||||
private:
|
||||
using storage_type = detail::call_once_storage<T>;
|
||||
|
||||
// Indicator of fast path for single-interpreter case.
|
||||
bool is_last_storage_valid() const {
|
||||
return is_initialized_by_at_least_one_interpreter_
|
||||
&& !detail::has_seen_non_main_interpreter();
|
||||
}
|
||||
|
||||
// Get the unique key for this storage instance in the interpreter's state dict.
|
||||
// The return type should not be `py::str` because PyObject is interpreter-dependent.
|
||||
std::string get_storage_key() const {
|
||||
// The instance is expected to be global static, so using its address as unique identifier.
|
||||
// The typical usage is like:
|
||||
//
|
||||
// PYBIND11_CONSTINIT static gil_safe_call_once_and_store<T> storage;
|
||||
//
|
||||
return PYBIND11_CALL_ONCE_STORAGE_KEY_PREFIX
|
||||
+ std::to_string(reinterpret_cast<std::uintptr_t>(this));
|
||||
}
|
||||
|
||||
// Get or create per-storage capsule in the current interpreter's state dict.
|
||||
// The storage is interpreter-dependent and will not be shared across interpreters.
|
||||
storage_type *get_or_create_storage_in_state_dict() {
|
||||
return detail::atomic_get_or_create_in_state_dict<storage_type>(get_storage_key().c_str())
|
||||
.first;
|
||||
}
|
||||
|
||||
// No storage needed when subinterpreter support is enabled.
|
||||
// The actual storage is stored in the per-interpreter state dict via
|
||||
// `get_or_create_storage_in_state_dict()`.
|
||||
|
||||
// Fast local cache to avoid repeated lookups when there are no multiple interpreters.
|
||||
// This is only valid if there is a single interpreter. Otherwise, it is not used.
|
||||
// WARNING: We cannot use thread local cache similar to `internals_pp_manager::internals_p_tls`
|
||||
// because the thread local storage cannot be explicitly invalidated when interpreters
|
||||
// are destroyed (unlike `internals_pp_manager` which has explicit hooks for that).
|
||||
T *last_storage_ptr_ = nullptr;
|
||||
// This flag is true if the value has been initialized by any interpreter (may not be the
|
||||
// current one).
|
||||
detail::atomic_bool is_initialized_by_at_least_one_interpreter_{false};
|
||||
};
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
||||
@@ -416,7 +416,7 @@ struct npy_format_descriptor_name<T, enable_if_t<is_complex<T>::value>> {
|
||||
|| std::is_same<typename T::value_type, const double>::value
|
||||
> (const_name("numpy.complex")
|
||||
+ const_name<sizeof(typename T::value_type) * 16>(),
|
||||
const_name("numpy.longcomplex"));
|
||||
const_name("numpy.clongdouble"));
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -1860,7 +1860,7 @@ public:
|
||||
using value_type = container_type::value_type;
|
||||
using size_type = container_type::size_type;
|
||||
|
||||
common_iterator() : m_strides() {}
|
||||
common_iterator() = default;
|
||||
|
||||
common_iterator(void *ptr, const container_type &strides, const container_type &shape)
|
||||
: p_ptr(reinterpret_cast<char *>(ptr)), m_strides(strides.size()) {
|
||||
|
||||
@@ -43,6 +43,12 @@ PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
|
||||
# include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_if_constexpr) && __cpp_if_constexpr >= 201606
|
||||
# define PYBIND11_MAYBE_CONSTEXPR constexpr
|
||||
#else
|
||||
# define PYBIND11_MAYBE_CONSTEXPR
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
/* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning
|
||||
@@ -159,7 +165,7 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel
|
||||
pybind11_fail("Internal error while parsing type signature (1)");
|
||||
}
|
||||
if (auto *tinfo = detail::get_type_info(*t)) {
|
||||
handle th((PyObject *) tinfo->type);
|
||||
handle th(reinterpret_cast<PyObject *>(tinfo->type));
|
||||
signature += th.attr("__module__").cast<std::string>() + "."
|
||||
+ th.attr("__qualname__").cast<std::string>();
|
||||
} else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) {
|
||||
@@ -642,7 +648,7 @@ protected:
|
||||
|
||||
rec->signature = guarded_strdup(signature.c_str());
|
||||
rec->args.shrink_to_fit();
|
||||
rec->nargs = (std::uint16_t) args;
|
||||
rec->nargs = static_cast<std::uint16_t>(args);
|
||||
|
||||
if (rec->sibling && PYBIND11_INSTANCE_METHOD_CHECK(rec->sibling.ptr())) {
|
||||
rec->sibling = PYBIND11_INSTANCE_METHOD_GET_FUNCTION(rec->sibling.ptr());
|
||||
@@ -678,10 +684,10 @@ protected:
|
||||
rec->def->ml_name = rec->name;
|
||||
rec->def->ml_meth
|
||||
= reinterpret_cast<PyCFunction>(reinterpret_cast<void (*)()>(dispatcher));
|
||||
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
|
||||
rec->def->ml_flags = METH_FASTCALL | METH_KEYWORDS;
|
||||
|
||||
object py_func_rec = detail::function_record_PyObject_New();
|
||||
((detail::function_record_PyObject *) py_func_rec.ptr())->cpp_func_rec
|
||||
(reinterpret_cast<detail::function_record_PyObject *>(py_func_rec.ptr()))->cpp_func_rec
|
||||
= unique_rec.release();
|
||||
guarded_strdup.release();
|
||||
|
||||
@@ -715,8 +721,8 @@ protected:
|
||||
// chain.
|
||||
chain_start = rec;
|
||||
rec->next = chain;
|
||||
auto *py_func_rec
|
||||
= (detail::function_record_PyObject *) PyCFunction_GET_SELF(m_ptr);
|
||||
auto *py_func_rec = reinterpret_cast<detail::function_record_PyObject *>(
|
||||
PyCFunction_GET_SELF(m_ptr));
|
||||
py_func_rec->cpp_func_rec = unique_rec.release();
|
||||
guarded_strdup.release();
|
||||
} else {
|
||||
@@ -776,7 +782,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
auto *func = (PyCFunctionObject *) m_ptr;
|
||||
auto *func = reinterpret_cast<PyCFunctionObject *>(m_ptr);
|
||||
// Install docstring if it's non-empty (when at least one option is enabled)
|
||||
auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str());
|
||||
std::free(const_cast<char *>(PYBIND11_PYCFUNCTION_GET_DOC(func)));
|
||||
@@ -811,9 +817,9 @@ protected:
|
||||
// so they cannot be freed. Once the function has been created, they can.
|
||||
// Check `make_function_record` for more details.
|
||||
if (free_strings) {
|
||||
std::free((char *) rec->name);
|
||||
std::free((char *) rec->doc);
|
||||
std::free((char *) rec->signature);
|
||||
std::free(rec->name);
|
||||
std::free(rec->doc);
|
||||
std::free(rec->signature);
|
||||
for (auto &arg : rec->args) {
|
||||
std::free(const_cast<char *>(arg.name));
|
||||
std::free(const_cast<char *>(arg.descr));
|
||||
@@ -841,7 +847,8 @@ protected:
|
||||
}
|
||||
|
||||
/// Main dispatch logic for calls to functions bound using pybind11
|
||||
static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) {
|
||||
static PyObject *
|
||||
dispatcher(PyObject *self, PyObject *const *args_in_arr, size_t nargsf, PyObject *kwnames_in) {
|
||||
using namespace detail;
|
||||
const function_record *overloads = function_record_ptr_from_PyObject(self);
|
||||
assert(overloads != nullptr);
|
||||
@@ -851,9 +858,9 @@ protected:
|
||||
|
||||
/* Need to know how many arguments + keyword arguments there are to pick the right
|
||||
overload */
|
||||
const auto n_args_in = (size_t) PyTuple_GET_SIZE(args_in);
|
||||
const auto n_args_in = static_cast<size_t>(PyVectorcall_NARGS(nargsf));
|
||||
|
||||
handle parent = n_args_in > 0 ? PyTuple_GET_ITEM(args_in, 0) : nullptr,
|
||||
handle parent = n_args_in > 0 ? args_in_arr[0] : nullptr,
|
||||
result = PYBIND11_TRY_NEXT_OVERLOAD;
|
||||
|
||||
auto self_value_and_holder = value_and_holder();
|
||||
@@ -865,7 +872,8 @@ protected:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto *const tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
|
||||
auto *const tinfo
|
||||
= get_type_info(reinterpret_cast<PyTypeObject *>(overloads->scope.ptr()));
|
||||
auto *const pi = reinterpret_cast<instance *>(parent.ptr());
|
||||
self_value_and_holder = pi->get_value_and_holder(tinfo, true);
|
||||
|
||||
@@ -941,7 +949,7 @@ protected:
|
||||
self_value_and_holder.type->dealloc(self_value_and_holder);
|
||||
}
|
||||
|
||||
call.init_self = PyTuple_GET_ITEM(args_in, 0);
|
||||
call.init_self = args_in_arr[0];
|
||||
call.args.emplace_back(reinterpret_cast<PyObject *>(&self_value_and_holder));
|
||||
call.args_convert.push_back(false);
|
||||
++args_copied;
|
||||
@@ -952,17 +960,24 @@ protected:
|
||||
for (; args_copied < args_to_copy; ++args_copied) {
|
||||
const argument_record *arg_rec
|
||||
= args_copied < func.args.size() ? &func.args[args_copied] : nullptr;
|
||||
if (kwargs_in && arg_rec && arg_rec->name
|
||||
&& dict_getitemstring(kwargs_in, arg_rec->name)) {
|
||||
|
||||
/* if the argument is listed in the call site's kwargs, but the argument is
|
||||
also fulfilled positionally, then the call can't match this overload. for
|
||||
example, the call site is: foo(0, key=1) but our overload is foo(key:int) then
|
||||
this call can't be for us, because it would be invalid.
|
||||
*/
|
||||
if (kwnames_in && arg_rec && arg_rec->name
|
||||
&& keyword_index(kwnames_in, arg_rec->name) >= 0) {
|
||||
bad_arg = true;
|
||||
break;
|
||||
}
|
||||
|
||||
handle arg(PyTuple_GET_ITEM(args_in, args_copied));
|
||||
handle arg(args_in_arr[args_copied]);
|
||||
if (arg_rec && !arg_rec->none && arg.is_none()) {
|
||||
bad_arg = true;
|
||||
break;
|
||||
}
|
||||
|
||||
call.args.push_back(arg);
|
||||
call.args_convert.push_back(arg_rec ? arg_rec->convert : true);
|
||||
}
|
||||
@@ -974,20 +989,12 @@ protected:
|
||||
// to copy the rest into a py::args argument.
|
||||
size_t positional_args_copied = args_copied;
|
||||
|
||||
// We'll need to copy this if we steal some kwargs for defaults
|
||||
dict kwargs = reinterpret_borrow<dict>(kwargs_in);
|
||||
|
||||
// 1.5. Fill in any missing pos_only args from defaults if they exist
|
||||
if (args_copied < func.nargs_pos_only) {
|
||||
for (; args_copied < func.nargs_pos_only; ++args_copied) {
|
||||
const auto &arg_rec = func.args[args_copied];
|
||||
handle value;
|
||||
|
||||
if (arg_rec.value) {
|
||||
value = arg_rec.value;
|
||||
}
|
||||
if (value) {
|
||||
call.args.push_back(value);
|
||||
call.args.push_back(arg_rec.value);
|
||||
call.args_convert.push_back(arg_rec.convert);
|
||||
} else {
|
||||
break;
|
||||
@@ -1000,46 +1007,42 @@ protected:
|
||||
}
|
||||
|
||||
// 2. Check kwargs and, failing that, defaults that may help complete the list
|
||||
small_vector<bool, arg_vector_small_size> used_kwargs(
|
||||
kwnames_in ? static_cast<size_t>(PyTuple_GET_SIZE(kwnames_in)) : 0, false);
|
||||
size_t used_kwargs_count = 0;
|
||||
if (args_copied < num_args) {
|
||||
bool copied_kwargs = false;
|
||||
|
||||
for (; args_copied < num_args; ++args_copied) {
|
||||
const auto &arg_rec = func.args[args_copied];
|
||||
|
||||
handle value;
|
||||
if (kwargs_in && arg_rec.name) {
|
||||
value = dict_getitemstring(kwargs.ptr(), arg_rec.name);
|
||||
if (kwnames_in && arg_rec.name) {
|
||||
ssize_t i = keyword_index(kwnames_in, arg_rec.name);
|
||||
if (i >= 0) {
|
||||
value = args_in_arr[n_args_in + static_cast<size_t>(i)];
|
||||
used_kwargs.set(static_cast<size_t>(i), true);
|
||||
used_kwargs_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (value) {
|
||||
// Consume a kwargs value
|
||||
if (!copied_kwargs) {
|
||||
kwargs = reinterpret_steal<dict>(PyDict_Copy(kwargs.ptr()));
|
||||
copied_kwargs = true;
|
||||
}
|
||||
if (PyDict_DelItemString(kwargs.ptr(), arg_rec.name) == -1) {
|
||||
throw error_already_set();
|
||||
}
|
||||
} else if (arg_rec.value) {
|
||||
if (!value) {
|
||||
value = arg_rec.value;
|
||||
if (!value) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!arg_rec.none && value.is_none()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
// If we're at the py::args index then first insert a stub for it to be
|
||||
// replaced later
|
||||
if (func.has_args && call.args.size() == func.nargs_pos) {
|
||||
call.args.push_back(none());
|
||||
}
|
||||
|
||||
call.args.push_back(value);
|
||||
call.args_convert.push_back(arg_rec.convert);
|
||||
} else {
|
||||
break;
|
||||
// If we're at the py::args index then first insert a stub for it to be
|
||||
// replaced later
|
||||
if (func.has_args && call.args.size() == func.nargs_pos) {
|
||||
call.args.push_back(none());
|
||||
}
|
||||
|
||||
call.args.push_back(value);
|
||||
call.args_convert.push_back(arg_rec.convert);
|
||||
}
|
||||
|
||||
if (args_copied < num_args) {
|
||||
@@ -1049,47 +1052,50 @@ protected:
|
||||
}
|
||||
|
||||
// 3. Check everything was consumed (unless we have a kwargs arg)
|
||||
if (kwargs && !kwargs.empty() && !func.has_kwargs) {
|
||||
if (!func.has_kwargs && used_kwargs_count < used_kwargs.size()) {
|
||||
continue; // Unconsumed kwargs, but no py::kwargs argument to accept them
|
||||
}
|
||||
|
||||
// 4a. If we have a py::args argument, create a new tuple with leftovers
|
||||
if (func.has_args) {
|
||||
tuple extra_args;
|
||||
if (args_to_copy == 0) {
|
||||
// We didn't copy out any position arguments from the args_in tuple, so we
|
||||
// can reuse it directly without copying:
|
||||
extra_args = reinterpret_borrow<tuple>(args_in);
|
||||
} else if (positional_args_copied >= n_args_in) {
|
||||
extra_args = tuple(0);
|
||||
if (positional_args_copied >= n_args_in) {
|
||||
call.args_ref = tuple(0);
|
||||
} else {
|
||||
size_t args_size = n_args_in - positional_args_copied;
|
||||
extra_args = tuple(args_size);
|
||||
tuple extra_args(args_size);
|
||||
for (size_t i = 0; i < args_size; ++i) {
|
||||
extra_args[i] = PyTuple_GET_ITEM(args_in, positional_args_copied + i);
|
||||
extra_args[i] = args_in_arr[positional_args_copied + i];
|
||||
}
|
||||
call.args_ref = std::move(extra_args);
|
||||
}
|
||||
if (call.args.size() <= func.nargs_pos) {
|
||||
call.args.push_back(extra_args);
|
||||
call.args.push_back(call.args_ref);
|
||||
} else {
|
||||
call.args[func.nargs_pos] = extra_args;
|
||||
call.args[func.nargs_pos] = call.args_ref;
|
||||
}
|
||||
call.args_convert.push_back(false);
|
||||
call.args_ref = std::move(extra_args);
|
||||
}
|
||||
|
||||
// 4b. If we have a py::kwargs, pass on any remaining kwargs
|
||||
if (func.has_kwargs) {
|
||||
if (!kwargs.ptr()) {
|
||||
kwargs = dict(); // If we didn't get one, send an empty one
|
||||
dict kwargs;
|
||||
for (size_t i = 0; i < used_kwargs.size(); ++i) {
|
||||
if (!used_kwargs[i]) {
|
||||
// Cast values into handles before indexing into kwargs to ensure
|
||||
// well-defined evaluation order (MSVC C4866).
|
||||
handle arg_in_arr = args_in_arr[n_args_in + i],
|
||||
kwname = PyTuple_GET_ITEM(kwnames_in, i);
|
||||
kwargs[kwname] = arg_in_arr;
|
||||
}
|
||||
}
|
||||
call.args.push_back(kwargs);
|
||||
call.args_convert.push_back(false);
|
||||
call.kwargs_ref = std::move(kwargs);
|
||||
}
|
||||
|
||||
// 5. Put everything in a vector. Not technically step 5, we've been building it
|
||||
// in `call.args` all along.
|
||||
// 5. Put everything in a vector. Not technically step 5, we've been building it
|
||||
// in `call.args` all along.
|
||||
|
||||
#if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
|
||||
if (call.args.size() != func.nargs || call.args_convert.size() != func.nargs) {
|
||||
pybind11_fail("Internal error: function call dispatcher inserted wrong number "
|
||||
@@ -1220,40 +1226,37 @@ protected:
|
||||
msg += '\n';
|
||||
}
|
||||
msg += "\nInvoked with: ";
|
||||
auto args_ = reinterpret_borrow<tuple>(args_in);
|
||||
bool some_args = false;
|
||||
for (size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) {
|
||||
for (size_t ti = overloads->is_constructor ? 1 : 0; ti < n_args_in; ++ti) {
|
||||
if (!some_args) {
|
||||
some_args = true;
|
||||
} else {
|
||||
msg += ", ";
|
||||
}
|
||||
try {
|
||||
msg += pybind11::repr(args_[ti]);
|
||||
msg += pybind11::repr(args_in_arr[ti]);
|
||||
} catch (const error_already_set &) {
|
||||
msg += "<repr raised Error>";
|
||||
}
|
||||
}
|
||||
if (kwargs_in) {
|
||||
auto kwargs = reinterpret_borrow<dict>(kwargs_in);
|
||||
if (!kwargs.empty()) {
|
||||
if (some_args) {
|
||||
msg += "; ";
|
||||
if (kwnames_in && PyTuple_GET_SIZE(kwnames_in) > 0) {
|
||||
if (some_args) {
|
||||
msg += "; ";
|
||||
}
|
||||
msg += "kwargs: ";
|
||||
bool first = true;
|
||||
for (size_t i = 0; i < static_cast<size_t>(PyTuple_GET_SIZE(kwnames_in)); ++i) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
msg += ", ";
|
||||
}
|
||||
msg += "kwargs: ";
|
||||
bool first = true;
|
||||
for (const auto &kwarg : kwargs) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
msg += ", ";
|
||||
}
|
||||
msg += pybind11::str("{}=").format(kwarg.first);
|
||||
try {
|
||||
msg += pybind11::repr(kwarg.second);
|
||||
} catch (const error_already_set &) {
|
||||
msg += "<repr raised Error>";
|
||||
}
|
||||
msg += reinterpret_borrow<pybind11::str>(PyTuple_GET_ITEM(kwnames_in, i));
|
||||
msg += '=';
|
||||
try {
|
||||
msg += pybind11::repr(args_in_arr[n_args_in + i]);
|
||||
} catch (const error_already_set &) {
|
||||
msg += "<repr raised Error>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1288,6 +1291,28 @@ protected:
|
||||
}
|
||||
return result.ptr();
|
||||
}
|
||||
|
||||
static ssize_t keyword_index(PyObject *haystack, char const *needle) {
|
||||
/* kwargs is usually very small (<= 5 entries). The arg strings are typically interned.
|
||||
* CPython itself implements the search this way, first comparing all pointers ... which is
|
||||
* cheap and will work if the strings are interned. If it fails, then it falls back to a
|
||||
* second lexicographic check. This is wildly expensive for huge argument lists, but those
|
||||
* are incredibly rare so we optimize for the vastly common case of just a couple of args.
|
||||
*/
|
||||
auto n = PyTuple_GET_SIZE(haystack);
|
||||
auto s = reinterpret_steal<pybind11::str>(PyUnicode_InternFromString(needle));
|
||||
for (ssize_t i = 0; i < n; ++i) {
|
||||
if (PyTuple_GET_ITEM(haystack, i) == s.ptr()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
for (ssize_t i = 0; i < n; ++i) {
|
||||
if (PyUnicode_Compare(PyTuple_GET_ITEM(haystack, i), s.ptr()) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
@@ -1296,7 +1321,7 @@ PYBIND11_NAMESPACE_BEGIN(function_record_PyTypeObject_methods)
|
||||
|
||||
// This implementation needs the definition of `class cpp_function`.
|
||||
inline void tp_dealloc_impl(PyObject *self) {
|
||||
auto *py_func_rec = (function_record_PyObject *) self;
|
||||
auto *py_func_rec = reinterpret_cast<function_record_PyObject *>(self);
|
||||
cpp_function::destruct(py_func_rec->cpp_func_rec);
|
||||
py_func_rec->cpp_func_rec = nullptr;
|
||||
}
|
||||
@@ -1669,7 +1694,7 @@ protected:
|
||||
|
||||
/* Register supplemental type information in C++ dict */
|
||||
auto *tinfo = new detail::type_info();
|
||||
tinfo->type = (PyTypeObject *) m_ptr;
|
||||
tinfo->type = reinterpret_cast<PyTypeObject *>(m_ptr);
|
||||
tinfo->cpptype = rec.type;
|
||||
tinfo->type_size = rec.type_size;
|
||||
tinfo->type_align = rec.type_align;
|
||||
@@ -1704,7 +1729,7 @@ protected:
|
||||
PYBIND11_WARNING_DISABLE_GCC("-Warray-bounds")
|
||||
PYBIND11_WARNING_DISABLE_GCC("-Wstringop-overread")
|
||||
#endif
|
||||
internals.registered_types_py[(PyTypeObject *) m_ptr] = {tinfo};
|
||||
internals.registered_types_py[reinterpret_cast<PyTypeObject *>(m_ptr)] = {tinfo};
|
||||
PYBIND11_WARNING_POP
|
||||
});
|
||||
|
||||
@@ -1712,7 +1737,8 @@ protected:
|
||||
mark_parents_nonsimple(tinfo->type);
|
||||
tinfo->simple_ancestors = false;
|
||||
} else if (rec.bases.size() == 1) {
|
||||
auto *parent_tinfo = get_type_info((PyTypeObject *) rec.bases[0].ptr());
|
||||
auto *parent_tinfo
|
||||
= get_type_info(reinterpret_cast<PyTypeObject *>(rec.bases[0].ptr()));
|
||||
assert(parent_tinfo != nullptr);
|
||||
bool parent_simple_ancestors = parent_tinfo->simple_ancestors;
|
||||
tinfo->simple_ancestors = parent_simple_ancestors;
|
||||
@@ -1731,17 +1757,17 @@ protected:
|
||||
void mark_parents_nonsimple(PyTypeObject *value) {
|
||||
auto t = reinterpret_borrow<tuple>(value->tp_bases);
|
||||
for (handle h : t) {
|
||||
auto *tinfo2 = get_type_info((PyTypeObject *) h.ptr());
|
||||
auto *tinfo2 = get_type_info(reinterpret_cast<PyTypeObject *>(h.ptr()));
|
||||
if (tinfo2) {
|
||||
tinfo2->simple_type = false;
|
||||
}
|
||||
mark_parents_nonsimple((PyTypeObject *) h.ptr());
|
||||
mark_parents_nonsimple(reinterpret_cast<PyTypeObject *>(h.ptr()));
|
||||
}
|
||||
}
|
||||
|
||||
void install_buffer_funcs(buffer_info *(*get_buffer)(PyObject *, void *),
|
||||
void *get_buffer_data) {
|
||||
auto *type = (PyHeapTypeObject *) m_ptr;
|
||||
auto *type = reinterpret_cast<PyHeapTypeObject *>(m_ptr);
|
||||
auto *tinfo = detail::get_type_info(&type->ht_type);
|
||||
|
||||
if (!type->ht_type.tp_as_buffer) {
|
||||
@@ -1763,8 +1789,8 @@ protected:
|
||||
const auto is_static = (rec_func != nullptr) && !(rec_func->is_method && rec_func->scope);
|
||||
const auto has_doc = (rec_func != nullptr) && (rec_func->doc != nullptr)
|
||||
&& pybind11::options::show_user_defined_docstrings();
|
||||
auto property = handle(
|
||||
(PyObject *) (is_static ? get_internals().static_property_type : &PyProperty_Type));
|
||||
auto property = handle(reinterpret_cast<PyObject *>(
|
||||
is_static ? get_internals().static_property_type : &PyProperty_Type));
|
||||
attr(name) = property(fget.ptr() ? fget : none(),
|
||||
fset.ptr() ? fset : none(),
|
||||
/*deleter*/ none(),
|
||||
@@ -2486,8 +2512,7 @@ private:
|
||||
static void init_holder_from_existing(const detail::value_and_holder &v_h,
|
||||
const holder_type *holder_ptr,
|
||||
std::true_type /*is_copy_constructible*/) {
|
||||
new (std::addressof(v_h.holder<holder_type>()))
|
||||
holder_type(*reinterpret_cast<const holder_type *>(holder_ptr));
|
||||
new (std::addressof(v_h.holder<holder_type>())) holder_type(*holder_ptr);
|
||||
}
|
||||
|
||||
static void init_holder_from_existing(const detail::value_and_holder &v_h,
|
||||
@@ -2699,8 +2724,9 @@ struct enum_base {
|
||||
|
||||
PYBIND11_NOINLINE void init(bool is_arithmetic, bool is_convertible) {
|
||||
m_base.attr("__entries") = dict();
|
||||
auto property = handle((PyObject *) &PyProperty_Type);
|
||||
auto static_property = handle((PyObject *) get_internals().static_property_type);
|
||||
auto property = handle(reinterpret_cast<PyObject *>(&PyProperty_Type));
|
||||
auto static_property
|
||||
= handle(reinterpret_cast<PyObject *>(get_internals().static_property_type));
|
||||
|
||||
m_base.attr("__repr__") = cpp_function(
|
||||
[](const object &arg) -> str {
|
||||
@@ -2731,7 +2757,7 @@ struct enum_base {
|
||||
[](handle arg) -> std::string {
|
||||
std::string docstring;
|
||||
dict entries = arg.attr("__entries");
|
||||
if (((PyTypeObject *) arg.ptr())->tp_doc) {
|
||||
if ((reinterpret_cast<PyTypeObject *>(arg.ptr()))->tp_doc) {
|
||||
docstring += std::string(
|
||||
reinterpret_cast<PyTypeObject *>(arg.ptr())->tp_doc);
|
||||
docstring += "\n\n";
|
||||
@@ -2857,7 +2883,7 @@ struct enum_base {
|
||||
dict entries = m_base.attr("__entries");
|
||||
str name(name_);
|
||||
if (entries.contains(name)) {
|
||||
std::string type_name = (std::string) str(m_base.attr("__name__"));
|
||||
std::string type_name = std::string(str(m_base.attr("__name__")));
|
||||
throw value_error(std::move(type_name) + ": element \"" + std::string(name_)
|
||||
+ "\" already exists!");
|
||||
}
|
||||
@@ -3052,7 +3078,7 @@ all_type_info_get_cache(PyTypeObject *type) {
|
||||
if (res.second) {
|
||||
// New cache entry created; set up a weak reference to automatically remove it if the type
|
||||
// gets destroyed:
|
||||
weakref((PyObject *) type, cpp_function([type](handle wr) {
|
||||
weakref(reinterpret_cast<PyObject *>(type), cpp_function([type](handle wr) {
|
||||
with_internals([type](internals &internals) {
|
||||
internals.registered_types_py.erase(type);
|
||||
|
||||
@@ -3293,7 +3319,7 @@ void implicitly_convertible() {
|
||||
}
|
||||
tuple args(1);
|
||||
args[0] = obj;
|
||||
PyObject *result = PyObject_Call((PyObject *) type, args.ptr(), nullptr);
|
||||
PyObject *result = PyObject_Call(reinterpret_cast<PyObject *>(type), args.ptr(), nullptr);
|
||||
if (result == nullptr) {
|
||||
PyErr_Clear();
|
||||
}
|
||||
@@ -3509,7 +3535,7 @@ get_type_override(const void *this_ptr, const type_info *this_type, const char *
|
||||
if (frame != nullptr) {
|
||||
PyCodeObject *f_code = PyFrame_GetCode(frame);
|
||||
// f_code is guaranteed to not be NULL
|
||||
if ((std::string) str(f_code->co_name) == name && f_code->co_argcount > 0) {
|
||||
if (std::string(str(f_code->co_name)) == name && f_code->co_argcount > 0) {
|
||||
# if PY_VERSION_HEX >= 0x030d0000
|
||||
PyObject *locals = PyEval_GetFrameLocals();
|
||||
# else
|
||||
@@ -3604,13 +3630,15 @@ function get_override(const T *this_ptr, const char *name) {
|
||||
auto o = override(__VA_ARGS__); \
|
||||
PYBIND11_WARNING_PUSH \
|
||||
PYBIND11_WARNING_DISABLE_MSVC(4127) \
|
||||
if (pybind11::detail::cast_is_temporary_value_reference<ret_type>::value \
|
||||
if PYBIND11_MAYBE_CONSTEXPR ( \
|
||||
pybind11::detail::cast_is_temporary_value_reference<ret_type>::value \
|
||||
&& !pybind11::detail::is_same_ignoring_cvref<ret_type, PyObject *>::value) { \
|
||||
static pybind11::detail::override_caster_t<ret_type> caster; \
|
||||
return pybind11::detail::cast_ref<ret_type>(std::move(o), caster); \
|
||||
} else { \
|
||||
return pybind11::detail::cast_safe<ret_type>(std::move(o)); \
|
||||
} \
|
||||
PYBIND11_WARNING_POP \
|
||||
return pybind11::detail::cast_safe<ret_type>(std::move(o)); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
|
||||
@@ -547,8 +547,13 @@ struct error_fetch_and_normalize {
|
||||
// The presence of __notes__ is likely due to exception normalization
|
||||
// errors, although that is not necessarily true, therefore insert a
|
||||
// hint only:
|
||||
if (PyObject_HasAttrString(m_value.ptr(), "__notes__")) {
|
||||
const int has_notes = PyObject_HasAttrString(m_value.ptr(), "__notes__");
|
||||
if (has_notes == 1) {
|
||||
m_lazy_error_string += "[WITH __notes__]";
|
||||
} else if (has_notes == -1) {
|
||||
// Ignore secondary errors when probing for __notes__ to avoid leaking a
|
||||
// spurious exception while still reporting the original error.
|
||||
PyErr_Clear();
|
||||
}
|
||||
#else
|
||||
// PyErr_NormalizeException() may change the exception type if there are cascading
|
||||
@@ -861,7 +866,7 @@ bool isinstance(handle obj) {
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool isinstance<handle>(handle) = delete;
|
||||
bool isinstance<handle>(handle) = delete;
|
||||
template <>
|
||||
inline bool isinstance<object>(handle obj) {
|
||||
return obj.ptr() != nullptr;
|
||||
@@ -992,9 +997,11 @@ inline PyObject *dict_getitem(PyObject *v, PyObject *key) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// PyDict_GetItemStringRef was added in Python 3.13.0a1.
|
||||
// See also: https://github.com/python/pythoncapi-compat/blob/main/pythoncapi_compat.h
|
||||
inline PyObject *dict_getitemstringref(PyObject *v, const char *key) {
|
||||
#if PY_VERSION_HEX >= 0x030D0000
|
||||
PyObject *rv;
|
||||
#if PY_VERSION_HEX >= 0x030D00A1
|
||||
PyObject *rv = nullptr;
|
||||
if (PyDict_GetItemStringRef(v, key, &rv) < 0) {
|
||||
throw error_already_set();
|
||||
}
|
||||
@@ -1009,6 +1016,46 @@ inline PyObject *dict_getitemstringref(PyObject *v, const char *key) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline PyObject *dict_setdefaultstring(PyObject *v, const char *key, PyObject *defaultobj) {
|
||||
PyObject *kv = PyUnicode_FromString(key);
|
||||
if (kv == nullptr) {
|
||||
throw error_already_set();
|
||||
}
|
||||
|
||||
PyObject *rv = PyDict_SetDefault(v, kv, defaultobj);
|
||||
Py_DECREF(kv);
|
||||
if (rv == nullptr) {
|
||||
throw error_already_set();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// PyDict_SetDefaultRef was added in Python 3.13.0a4.
|
||||
// See also: https://github.com/python/pythoncapi-compat/blob/main/pythoncapi_compat.h
|
||||
inline PyObject *dict_setdefaultstringref(PyObject *v, const char *key, PyObject *defaultobj) {
|
||||
#if PY_VERSION_HEX >= 0x030D00A4
|
||||
PyObject *kv = PyUnicode_FromString(key);
|
||||
if (kv == nullptr) {
|
||||
throw error_already_set();
|
||||
}
|
||||
|
||||
PyObject *rv = nullptr;
|
||||
if (PyDict_SetDefaultRef(v, kv, defaultobj, &rv) < 0) {
|
||||
Py_DECREF(kv);
|
||||
throw error_already_set();
|
||||
}
|
||||
Py_DECREF(kv);
|
||||
return rv;
|
||||
#else
|
||||
PyObject *rv = dict_setdefaultstring(v, key, defaultobj);
|
||||
if (rv == nullptr || PyErr_Occurred()) {
|
||||
throw error_already_set();
|
||||
}
|
||||
Py_XINCREF(rv);
|
||||
return rv;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Helper aliases/functions to support implicit casting of values given to python
|
||||
// accessors/methods. When given a pyobject, this simply returns the pyobject as-is; for other C++
|
||||
// type, the value goes through pybind11::cast(obj) to convert it to an `object`.
|
||||
@@ -1555,7 +1602,7 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
object value = {};
|
||||
object value;
|
||||
};
|
||||
|
||||
class type : public object {
|
||||
@@ -1563,7 +1610,9 @@ public:
|
||||
PYBIND11_OBJECT(type, object, PyType_Check)
|
||||
|
||||
/// Return a type handle from a handle or an object
|
||||
static handle handle_of(handle h) { return handle((PyObject *) Py_TYPE(h.ptr())); }
|
||||
static handle handle_of(handle h) {
|
||||
return handle(reinterpret_cast<PyObject *>(Py_TYPE(h.ptr())));
|
||||
}
|
||||
|
||||
/// Return a type object from a handle or an object
|
||||
static type of(handle h) { return type(type::handle_of(h), borrowed_t{}); }
|
||||
@@ -1641,7 +1690,13 @@ public:
|
||||
Return a string representation of the object. This is analogous to
|
||||
the ``str()`` function in Python.
|
||||
\endrst */
|
||||
explicit str(handle h) : object(raw_str(h.ptr()), stolen_t{}) {
|
||||
// Templatized to avoid ambiguity with str(const object&) for object-derived types.
|
||||
template <typename T,
|
||||
detail::enable_if_t<!std::is_base_of<object, detail::remove_cvref_t<T>>::value
|
||||
&& std::is_constructible<handle, T>::value,
|
||||
int>
|
||||
= 0>
|
||||
explicit str(T &&h) : object(raw_str(handle(std::forward<T>(h)).ptr()), stolen_t{}) {
|
||||
if (!m_ptr) {
|
||||
throw error_already_set();
|
||||
}
|
||||
@@ -1661,7 +1716,7 @@ public:
|
||||
if (PyBytes_AsStringAndSize(temp.ptr(), &buffer, &length) != 0) {
|
||||
throw error_already_set();
|
||||
}
|
||||
return std::string(buffer, (size_t) length);
|
||||
return std::string(buffer, static_cast<size_t>(length));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
@@ -1861,10 +1916,12 @@ template <typename Unsigned>
|
||||
Unsigned as_unsigned(PyObject *o) {
|
||||
if (sizeof(Unsigned) <= sizeof(unsigned long)) {
|
||||
unsigned long v = PyLong_AsUnsignedLong(o);
|
||||
return v == (unsigned long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
|
||||
return v == static_cast<unsigned long>(-1) && PyErr_Occurred() ? (Unsigned) -1
|
||||
: (Unsigned) v;
|
||||
}
|
||||
unsigned long long v = PyLong_AsUnsignedLongLong(o);
|
||||
return v == (unsigned long long) -1 && PyErr_Occurred() ? (Unsigned) -1 : (Unsigned) v;
|
||||
return v == static_cast<unsigned long long>(-1) && PyErr_Occurred() ? (Unsigned) -1
|
||||
: (Unsigned) v;
|
||||
}
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
@@ -1908,21 +1965,21 @@ public:
|
||||
PYBIND11_OBJECT_CVT(float_, object, PyFloat_Check, PyNumber_Float)
|
||||
// Allow implicit conversion from float/double:
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
float_(float value) : object(PyFloat_FromDouble((double) value), stolen_t{}) {
|
||||
float_(float value) : object(PyFloat_FromDouble(static_cast<double>(value)), stolen_t{}) {
|
||||
if (!m_ptr) {
|
||||
pybind11_fail("Could not allocate float object!");
|
||||
}
|
||||
}
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
float_(double value = .0) : object(PyFloat_FromDouble((double) value), stolen_t{}) {
|
||||
float_(double value = .0) : object(PyFloat_FromDouble(value), stolen_t{}) {
|
||||
if (!m_ptr) {
|
||||
pybind11_fail("Could not allocate float object!");
|
||||
}
|
||||
}
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator float() const { return (float) PyFloat_AsDouble(m_ptr); }
|
||||
operator float() const { return static_cast<float>(PyFloat_AsDouble(m_ptr)); }
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
operator double() const { return (double) PyFloat_AsDouble(m_ptr); }
|
||||
operator double() const { return PyFloat_AsDouble(m_ptr); }
|
||||
};
|
||||
|
||||
class weakref : public object {
|
||||
@@ -2122,7 +2179,7 @@ public:
|
||||
pybind11_fail("Could not allocate tuple object!");
|
||||
}
|
||||
}
|
||||
size_t size() const { return (size_t) PyTuple_Size(m_ptr); }
|
||||
size_t size() const { return static_cast<size_t>(PyTuple_Size(m_ptr)); }
|
||||
bool empty() const { return size() == 0; }
|
||||
detail::tuple_accessor operator[](size_t index) const { return {*this, index}; }
|
||||
template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
|
||||
@@ -2156,7 +2213,7 @@ public:
|
||||
typename collector = detail::deferred_t<detail::unpacking_collector<>, Args...>>
|
||||
explicit dict(Args &&...args) : dict(collector(std::forward<Args>(args)...).kwargs()) {}
|
||||
|
||||
size_t size() const { return (size_t) PyDict_Size(m_ptr); }
|
||||
size_t size() const { return static_cast<size_t>(PyDict_Size(m_ptr)); }
|
||||
bool empty() const { return size() == 0; }
|
||||
detail::dict_iterator begin() const { return {*this, 0}; }
|
||||
detail::dict_iterator end() const { return {}; }
|
||||
@@ -2176,7 +2233,8 @@ private:
|
||||
if (PyDict_Check(op)) {
|
||||
return handle(op).inc_ref().ptr();
|
||||
}
|
||||
return PyObject_CallFunctionObjArgs((PyObject *) &PyDict_Type, op, nullptr);
|
||||
return PyObject_CallFunctionObjArgs(
|
||||
reinterpret_cast<PyObject *>(&PyDict_Type), op, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2188,7 +2246,7 @@ public:
|
||||
if (result == -1) {
|
||||
throw error_already_set();
|
||||
}
|
||||
return (size_t) result;
|
||||
return static_cast<size_t>(result);
|
||||
}
|
||||
bool empty() const { return size() == 0; }
|
||||
detail::sequence_accessor operator[](size_t index) const { return {*this, index}; }
|
||||
@@ -2211,7 +2269,7 @@ public:
|
||||
pybind11_fail("Could not allocate list object!");
|
||||
}
|
||||
}
|
||||
size_t size() const { return (size_t) PyList_Size(m_ptr); }
|
||||
size_t size() const { return static_cast<size_t>(PyList_Size(m_ptr)); }
|
||||
bool empty() const { return size() == 0; }
|
||||
detail::list_accessor operator[](size_t index) const { return {*this, index}; }
|
||||
template <typename T, detail::enable_if_t<detail::is_pyobject<T>::value, int> = 0>
|
||||
@@ -2497,7 +2555,7 @@ inline size_t len(handle h) {
|
||||
if (result < 0) {
|
||||
throw error_already_set();
|
||||
}
|
||||
return (size_t) result;
|
||||
return static_cast<size_t>(result);
|
||||
}
|
||||
|
||||
/// Get the length hint of a Python object.
|
||||
@@ -2510,7 +2568,7 @@ inline size_t len_hint(handle h) {
|
||||
PyErr_Clear();
|
||||
return 0;
|
||||
}
|
||||
return (size_t) result;
|
||||
return static_cast<size_t>(result);
|
||||
}
|
||||
|
||||
inline str repr(handle h) {
|
||||
|
||||
@@ -244,7 +244,7 @@ void vector_modifiers(
|
||||
}
|
||||
|
||||
auto *seq = new Vector();
|
||||
seq->reserve((size_t) slicelength);
|
||||
seq->reserve(slicelength);
|
||||
|
||||
for (size_t i = 0; i < slicelength; ++i) {
|
||||
seq->push_back(v[start]);
|
||||
|
||||
@@ -20,15 +20,6 @@
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
inline PyInterpreterState *get_interpreter_state_unchecked() {
|
||||
auto cur_tstate = get_thread_state_unchecked();
|
||||
if (cur_tstate)
|
||||
return cur_tstate->interp;
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
class subinterpreter;
|
||||
|
||||
@@ -76,7 +67,7 @@ public:
|
||||
/// Create a new subinterpreter with the specified configuration
|
||||
/// @note This function acquires (and then releases) the main interpreter GIL, but the main
|
||||
/// interpreter and its GIL are not required to be held prior to calling this function.
|
||||
static inline subinterpreter create(PyInterpreterConfig const &cfg) {
|
||||
static subinterpreter create(PyInterpreterConfig const &cfg) {
|
||||
|
||||
error_scope err_scope;
|
||||
subinterpreter result;
|
||||
@@ -84,7 +75,7 @@ public:
|
||||
// we must hold the main GIL in order to create a subinterpreter
|
||||
subinterpreter_scoped_activate main_guard(main());
|
||||
|
||||
auto prev_tstate = PyThreadState_Get();
|
||||
auto *prev_tstate = PyThreadState_Get();
|
||||
|
||||
PyStatus status;
|
||||
|
||||
@@ -103,13 +94,13 @@ public:
|
||||
}
|
||||
|
||||
// this doesn't raise a normal Python exception, it provides an exit() status code.
|
||||
if (PyStatus_Exception(status)) {
|
||||
if (PyStatus_Exception(status) != 0) {
|
||||
pybind11_fail("failed to create new sub-interpreter");
|
||||
}
|
||||
|
||||
// upon success, the new interpreter is activated in this thread
|
||||
result.istate_ = result.creation_tstate_->interp;
|
||||
detail::get_num_interpreters_seen() += 1; // there are now many interpreters
|
||||
detail::has_seen_non_main_interpreter() = true;
|
||||
detail::get_internals(); // initialize internals.tstate, amongst other things...
|
||||
|
||||
// In 3.13+ this state should be deleted right away, and the memory will be reused for
|
||||
@@ -128,7 +119,7 @@ public:
|
||||
|
||||
/// Calls create() with a default configuration of an isolated interpreter that disallows fork,
|
||||
/// exec, and Python threads.
|
||||
static inline subinterpreter create() {
|
||||
static subinterpreter create() {
|
||||
// same as the default config in the python docs
|
||||
PyInterpreterConfig cfg;
|
||||
std::memset(&cfg, 0, sizeof(cfg));
|
||||
@@ -144,8 +135,8 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
PyThreadState *destroy_tstate;
|
||||
PyThreadState *old_tstate;
|
||||
PyThreadState *destroy_tstate = nullptr;
|
||||
PyThreadState *old_tstate = nullptr;
|
||||
|
||||
// Python 3.12 requires us to keep the original PyThreadState alive until we are ready to
|
||||
// destroy the interpreter. We prefer to use that to destroy the interpreter.
|
||||
@@ -173,7 +164,7 @@ public:
|
||||
old_tstate = PyThreadState_Swap(destroy_tstate);
|
||||
#endif
|
||||
|
||||
bool switch_back = old_tstate && old_tstate->interp != istate_;
|
||||
bool switch_back = (old_tstate != nullptr) && old_tstate->interp != istate_;
|
||||
|
||||
// Internals always exists in the subinterpreter, this class enforces it when it creates
|
||||
// the subinterpreter. Even if it didn't, this only creates the pointer-to-pointer, not the
|
||||
@@ -190,8 +181,9 @@ public:
|
||||
detail::get_local_internals_pp_manager().destroy();
|
||||
|
||||
// switch back to the old tstate and old GIL (if there was one)
|
||||
if (switch_back)
|
||||
if (switch_back) {
|
||||
PyThreadState_Swap(old_tstate);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a handle to the main interpreter that can be used with subinterpreter_scoped_activate
|
||||
@@ -214,11 +206,11 @@ public:
|
||||
|
||||
/// Get the numerical identifier for the sub-interpreter
|
||||
int64_t id() const {
|
||||
if (istate_ != nullptr)
|
||||
if (istate_ != nullptr) {
|
||||
return PyInterpreterState_GetID(istate_);
|
||||
else
|
||||
return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
|
||||
// here.
|
||||
}
|
||||
return -1; // CPython uses one-up numbers from 0, so negative should be safe to return
|
||||
// here.
|
||||
}
|
||||
|
||||
/// Get the interpreter's state dict. This interpreter's GIL must be held before calling!
|
||||
|
||||
@@ -38,7 +38,7 @@ requires-python = ">=3.8"
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/pybind/pybind11"
|
||||
Documentation = "https://pybind11.readthedocs.io/"
|
||||
"Bug Tracker" = "https://github.com/pybind/pybind11/issues"
|
||||
"Issue Tracker" = "https://github.com/pybind/pybind11/issues"
|
||||
Discussions = "https://github.com/pybind/pybind11/discussions"
|
||||
Changelog = "https://pybind11.readthedocs.io/en/latest/changelog.html"
|
||||
Chat = "https://gitter.im/pybind/Lobby"
|
||||
@@ -152,6 +152,8 @@ extend-select = [
|
||||
"C4", # flake8-comprehensions
|
||||
"EM", # flake8-errmsg
|
||||
"ICN", # flake8-import-conventions
|
||||
"ISC", # flake8-implicit-str-concat
|
||||
"PERF", # Perflint
|
||||
"PGH", # pygrep-hooks
|
||||
"PIE", # flake8-pie
|
||||
"PL", # pylint
|
||||
@@ -182,3 +184,26 @@ isort.required-imports = ["from __future__ import annotations"]
|
||||
|
||||
[tool.repo-review]
|
||||
ignore = ["PP"]
|
||||
|
||||
[tool.typos]
|
||||
files.extend-exclude = ["/cpython"]
|
||||
|
||||
[tool.typos.default.extend-identifiers]
|
||||
ser_no = "ser_no"
|
||||
SerNo = "SerNo"
|
||||
StrLits = "StrLits"
|
||||
|
||||
[tool.typos.default.extend-words]
|
||||
nd = "nd"
|
||||
valu = "valu"
|
||||
fo = "fo"
|
||||
quater = "quater"
|
||||
optin = "optin"
|
||||
othr = "othr"
|
||||
|
||||
#[tool.typos.type.cpp.extend-words]
|
||||
setp = "setp"
|
||||
ot = "ot"
|
||||
|
||||
[tool.typos.type.json.extend-words]
|
||||
ba = "ba"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# All rights reserved. Use of this source code is governed by a
|
||||
# BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
# Filter out items; print an optional message if any items filtered. This ignores extensions.
|
||||
#
|
||||
@@ -545,7 +545,9 @@ source_group(
|
||||
FILES ${PYBIND11_HEADERS})
|
||||
|
||||
# Make sure pytest is found or produce a warning
|
||||
pybind11_find_import(pytest VERSION 3.1)
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
pybind11_find_import(pytest VERSION 3.1)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
|
||||
# This is not used later in the build, so it's okay to regenerate each time.
|
||||
@@ -578,24 +580,46 @@ add_custom_target(
|
||||
USES_TERMINAL)
|
||||
|
||||
if(NOT PYBIND11_CUDA_TESTS)
|
||||
# This module doesn't get mixed with other test modules because those aren't subinterpreter safe.
|
||||
pybind11_add_module(mod_per_interpreter_gil THIN_LTO mod_per_interpreter_gil.cpp)
|
||||
pybind11_add_module(mod_shared_interpreter_gil THIN_LTO mod_shared_interpreter_gil.cpp)
|
||||
set_target_properties(mod_per_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"$<1:${CMAKE_CURRENT_BINARY_DIR}>")
|
||||
set_target_properties(mod_shared_interpreter_gil PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"$<1:${CMAKE_CURRENT_BINARY_DIR}>")
|
||||
if(PYBIND11_TEST_SMART_HOLDER)
|
||||
target_compile_definitions(
|
||||
mod_per_interpreter_gil
|
||||
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
|
||||
)
|
||||
target_compile_definitions(
|
||||
mod_shared_interpreter_gil
|
||||
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
|
||||
)
|
||||
set(PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES
|
||||
mod_per_interpreter_gil mod_shared_interpreter_gil mod_per_interpreter_gil_with_singleton)
|
||||
|
||||
# These modules don't get mixed with other test modules because those aren't subinterpreter safe.
|
||||
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
|
||||
pybind11_add_module("${mod}" THIN_LTO "${mod}.cpp")
|
||||
pybind11_enable_warnings("${mod}")
|
||||
endforeach()
|
||||
|
||||
# Put the built modules next to `pybind11_tests.so` so that the test scripts can find them.
|
||||
get_target_property(pybind11_tests_output_directory pybind11_tests LIBRARY_OUTPUT_DIRECTORY)
|
||||
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
|
||||
set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${pybind11_tests_output_directory}")
|
||||
# Also set config-specific output directories for multi-configuration generators (MSVC)
|
||||
if(DEFINED CMAKE_CONFIGURATION_TYPES)
|
||||
foreach(config ${CMAKE_CONFIGURATION_TYPES})
|
||||
string(TOUPPER ${config} config)
|
||||
set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config}
|
||||
"${pybind11_tests_output_directory}")
|
||||
endforeach()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(SKBUILD)
|
||||
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
|
||||
install(TARGETS "${mod}" LIBRARY DESTINATION .)
|
||||
endforeach()
|
||||
endif()
|
||||
add_dependencies(pytest mod_per_interpreter_gil mod_shared_interpreter_gil)
|
||||
|
||||
if(PYBIND11_TEST_SMART_HOLDER)
|
||||
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
|
||||
target_compile_definitions(
|
||||
"${mod}"
|
||||
PUBLIC
|
||||
-DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
add_dependencies(pytest ${PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES})
|
||||
endif()
|
||||
|
||||
if(PYBIND11_TEST_OVERRIDE)
|
||||
|
||||
@@ -304,10 +304,10 @@ def backport_typehints() -> Callable[[SanitizedString], SanitizedString]:
|
||||
if sys.version_info < (3, 10):
|
||||
d["typing_extensions.TypeGuard"] = "typing.TypeGuard"
|
||||
|
||||
def backport(sanatized_string: SanitizedString) -> SanitizedString:
|
||||
def backport(sanitized_string: SanitizedString) -> SanitizedString:
|
||||
for old, new in d.items():
|
||||
sanatized_string.string = sanatized_string.string.replace(old, new)
|
||||
sanitized_string.string = sanitized_string.string.replace(old, new)
|
||||
|
||||
return sanatized_string
|
||||
return sanitized_string
|
||||
|
||||
return backport
|
||||
|
||||
@@ -5,9 +5,11 @@ import sys
|
||||
import sysconfig
|
||||
|
||||
ANDROID = sys.platform.startswith("android")
|
||||
IOS = sys.platform.startswith("ios")
|
||||
LINUX = sys.platform.startswith("linux")
|
||||
MACOS = sys.platform.startswith("darwin")
|
||||
WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
|
||||
FREEBSD = sys.platform.startswith("freebsd")
|
||||
|
||||
CPYTHON = platform.python_implementation() == "CPython"
|
||||
PYPY = platform.python_implementation() == "PyPy"
|
||||
|
||||
147
tests/mod_per_interpreter_gil_with_singleton.cpp
Normal file
147
tests/mod_per_interpreter_gil_with_singleton.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
#ifdef PYBIND11_HAS_NATIVE_ENUM
|
||||
# include <pybind11/native_enum.h>
|
||||
#endif
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace mod_per_interpreter_gil_with_singleton {
|
||||
// A singleton class that holds references to certain Python objects
|
||||
// This singleton is per-interpreter using gil_safe_call_once_and_store
|
||||
class MySingleton {
|
||||
public:
|
||||
MySingleton() = default;
|
||||
~MySingleton() = default;
|
||||
MySingleton(const MySingleton &) = delete;
|
||||
MySingleton &operator=(const MySingleton &) = delete;
|
||||
MySingleton(MySingleton &&) = default;
|
||||
MySingleton &operator=(MySingleton &&) = default;
|
||||
|
||||
static MySingleton &get_instance() {
|
||||
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<MySingleton> storage;
|
||||
return storage
|
||||
.call_once_and_store_result([]() -> MySingleton {
|
||||
MySingleton instance{};
|
||||
|
||||
auto emplace = [&instance](const py::handle &obj) -> void {
|
||||
obj.inc_ref(); // Ensure the object is not GC'd while interpreter is alive
|
||||
instance.objects.emplace_back(obj);
|
||||
};
|
||||
|
||||
// Example objects to store in the singleton
|
||||
emplace(py::type::handle_of(py::none())); // static type
|
||||
emplace(py::type::handle_of(py::tuple())); // static type
|
||||
emplace(py::type::handle_of(py::list())); // static type
|
||||
emplace(py::type::handle_of(py::dict())); // static type
|
||||
emplace(py::module_::import("collections").attr("OrderedDict")); // static type
|
||||
emplace(py::module_::import("collections").attr("defaultdict")); // heap type
|
||||
emplace(py::module_::import("collections").attr("deque")); // heap type
|
||||
|
||||
assert(instance.objects.size() == 7);
|
||||
return instance;
|
||||
})
|
||||
.get_stored();
|
||||
}
|
||||
|
||||
std::vector<py::handle> &get_objects() { return objects; }
|
||||
|
||||
static void init() {
|
||||
// Ensure the singleton is created
|
||||
auto &instance = get_instance();
|
||||
(void) instance; // suppress unused variable warning
|
||||
assert(instance.objects.size() == 7);
|
||||
// Register cleanup at interpreter exit
|
||||
py::module_::import("atexit").attr("register")(py::cpp_function(&MySingleton::clear));
|
||||
}
|
||||
|
||||
static void clear() {
|
||||
auto &instance = get_instance();
|
||||
(void) instance; // suppress unused variable warning
|
||||
assert(instance.objects.size() == 7);
|
||||
for (const auto &obj : instance.objects) {
|
||||
obj.dec_ref();
|
||||
}
|
||||
instance.objects.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<py::handle> objects;
|
||||
};
|
||||
|
||||
class MyClass {
|
||||
public:
|
||||
explicit MyClass(py::ssize_t v) : value(v) {}
|
||||
py::ssize_t get_value() const { return value; }
|
||||
|
||||
private:
|
||||
py::ssize_t value;
|
||||
};
|
||||
|
||||
class MyGlobalError : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
class MyLocalError : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
enum class MyEnum : int {
|
||||
ONE = 1,
|
||||
TWO = 2,
|
||||
THREE = 3,
|
||||
};
|
||||
} // namespace mod_per_interpreter_gil_with_singleton
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_MODULE(mod_per_interpreter_gil_with_singleton,
|
||||
m,
|
||||
py::mod_gil_not_used(),
|
||||
py::multiple_interpreters::per_interpreter_gil()) {
|
||||
using namespace pybind11_tests::mod_per_interpreter_gil_with_singleton;
|
||||
|
||||
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = true;
|
||||
#else
|
||||
m.attr("defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT") = false;
|
||||
#endif
|
||||
|
||||
MySingleton::init();
|
||||
|
||||
// Ensure py::multiple_interpreters::per_interpreter_gil() works with singletons using
|
||||
// py::gil_safe_call_once_and_store
|
||||
m.def(
|
||||
"get_objects_in_singleton",
|
||||
[]() -> std::vector<py::handle> { return MySingleton::get_instance().get_objects(); },
|
||||
"Get the list of objects stored in the singleton");
|
||||
|
||||
// Ensure py::multiple_interpreters::per_interpreter_gil() works with class bindings
|
||||
py::class_<MyClass>(m, "MyClass")
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def("get_value", &MyClass::get_value);
|
||||
|
||||
// Ensure py::multiple_interpreters::per_interpreter_gil() works with global exceptions
|
||||
py::register_exception<MyGlobalError>(m, "MyGlobalError");
|
||||
// Ensure py::multiple_interpreters::per_interpreter_gil() works with local exceptions
|
||||
py::register_local_exception<MyLocalError>(m, "MyLocalError");
|
||||
|
||||
#ifdef PYBIND11_HAS_NATIVE_ENUM
|
||||
// Ensure py::multiple_interpreters::per_interpreter_gil() works with native_enum
|
||||
py::native_enum<MyEnum>(m, "MyEnum", "enum.IntEnum")
|
||||
.value("ONE", MyEnum::ONE)
|
||||
.value("TWO", MyEnum::TWO)
|
||||
.value("THREE", MyEnum::THREE)
|
||||
.finalize();
|
||||
#else
|
||||
py::enum_<MyEnum>(m, "MyEnum")
|
||||
.value("ONE", MyEnum::ONE)
|
||||
.value("TWO", MyEnum::TWO)
|
||||
.value("THREE", MyEnum::THREE);
|
||||
#endif
|
||||
}
|
||||
@@ -15,6 +15,8 @@ target_link_libraries(smart_holder_poc_test PRIVATE pybind11::headers Catch2::Ca
|
||||
add_custom_target(
|
||||
test_pure_cpp
|
||||
COMMAND "$<TARGET_FILE:smart_holder_poc_test>"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
|
||||
)
|
||||
|
||||
add_dependencies(check test_pure_cpp)
|
||||
|
||||
@@ -25,7 +25,9 @@ PYBIND11_FINDPYTHON = true
|
||||
|
||||
[tool.cibuildwheel]
|
||||
test-sources = ["tests", "pyproject.toml"]
|
||||
test-command = "python -m pytest -o timeout=0 -p no:cacheprovider tests"
|
||||
test-command = "python -m pytest -v -o timeout=120 -p no:cacheprovider tests"
|
||||
# Pyodide doesn't have signal.setitimer, so pytest-timeout can't work with timeout > 0
|
||||
pyodide.test-command = "python -m pytest -v -o timeout=0 -p no:cacheprovider tests"
|
||||
environment.PIP_ONLY_BINARY = "numpy,scipy"
|
||||
environment.PIP_PREFER_BINARY = "1"
|
||||
android.environment.ANDROID_API_LEVEL = "24" # Needed to include libc++ in the wheel.
|
||||
|
||||
@@ -7,8 +7,10 @@ numpy~=2.2.0; python_version=="3.10" and platform_python_implementation=="PyPy"
|
||||
numpy~=1.26.0; platform_python_implementation=="GraalVM" and sys_platform=="linux"
|
||||
numpy~=1.21.5; platform_python_implementation=="CPython" and python_version>="3.8" and python_version<"3.10"
|
||||
numpy~=1.22.2; platform_python_implementation=="CPython" and python_version=="3.10"
|
||||
numpy~=1.26.0; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13"
|
||||
numpy~=2.2.0; platform_python_implementation=="CPython" and python_version=="3.13"
|
||||
numpy~=1.26.0; platform_python_implementation=="CPython" and python_version>="3.11" and python_version<"3.13" and platform_machine!="ARM64"
|
||||
numpy>=2.3.0; platform_python_implementation=="CPython" and python_version>="3.11" and platform_machine=="ARM64"
|
||||
numpy~=2.2.0; platform_python_implementation=="CPython" and python_version=="3.13" and platform_machine!="ARM64"
|
||||
numpy==2.4.0; platform_python_implementation=="CPython" and python_version>="3.14"
|
||||
pytest>=6
|
||||
pytest-timeout
|
||||
scipy~=1.5.4; platform_python_implementation=="CPython" and python_version<"3.10"
|
||||
|
||||
@@ -367,7 +367,7 @@ TEST_SUBMODULE(builtin_casters, m) {
|
||||
m.def("complex_noconvert", [](std::complex<float> x) { return x; }, py::arg{}.noconvert());
|
||||
|
||||
// test int vs. long (Python 2)
|
||||
m.def("int_cast", []() { return (int) 42; });
|
||||
m.def("int_cast", []() { return 42; });
|
||||
m.def("long_cast", []() { return (long) 42; });
|
||||
m.def("longlong_cast", []() { return ULLONG_MAX; });
|
||||
|
||||
|
||||
@@ -112,8 +112,8 @@ def test_pass_unique_ptr_disowns(pass_f, rtrn_f, expected):
|
||||
pass_f(obj)
|
||||
assert str(exc_info.value) == (
|
||||
"Missing value for wrapped C++ type"
|
||||
+ " `pybind11_tests::class_sh_basic::atyp`:"
|
||||
+ " Python instance was disowned."
|
||||
" `pybind11_tests::class_sh_basic::atyp`:"
|
||||
" Python instance was disowned."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ struct PySpBase : SpBase, py::trampoline_self_life_support {
|
||||
struct SpBaseTester {
|
||||
std::shared_ptr<SpBase> get_object() const { return m_obj; }
|
||||
void set_object(std::shared_ptr<SpBase> obj) { m_obj = std::move(obj); }
|
||||
bool is_base_used() { return m_obj->is_base_used(); }
|
||||
bool has_instance() { return (bool) m_obj; }
|
||||
bool has_python_instance() { return m_obj && m_obj->has_python_instance(); }
|
||||
bool is_base_used() const { return m_obj->is_base_used(); }
|
||||
bool has_instance() const { return (bool) m_obj; }
|
||||
bool has_python_instance() const { return m_obj && m_obj->has_python_instance(); }
|
||||
void set_nonpython_instance() { m_obj = std::make_shared<SpBase>(); }
|
||||
std::shared_ptr<SpBase> m_obj;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
project(test_installed_embed CXX)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
project(test_installed_function CXX)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
project(test_installed_target CXX)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
project(test_subdirectory_embed CXX)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
project(test_subdirectory_function CXX)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
project(test_subdirectory_target CXX)
|
||||
|
||||
|
||||
@@ -60,7 +60,9 @@ add_custom_target(
|
||||
test_cross_module_rtti
|
||||
COMMAND "$<TARGET_FILE:test_cross_module_rtti_main>"
|
||||
DEPENDS test_cross_module_rtti_main
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:test_cross_module_rtti_main>")
|
||||
WORKING_DIRECTORY "$<TARGET_FILE_DIR:test_cross_module_rtti_main>"
|
||||
USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
|
||||
)
|
||||
|
||||
set_target_properties(test_cross_module_rtti_bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_docstring_options():
|
||||
assert not m.DocstringTestFoo.__doc__
|
||||
assert not m.DocstringTestFoo.value_prop.__doc__
|
||||
|
||||
# Check existig behaviour of enum docstings
|
||||
# Check existing behaviour of enum docstings
|
||||
assert (
|
||||
m.DocstringTestEnum1.__doc__
|
||||
== "Enum docstring\n\nMembers:\n\n Member1\n\n Member2"
|
||||
|
||||
@@ -76,9 +76,9 @@ def test_cross_module_exceptions(msg):
|
||||
|
||||
# TODO: FIXME
|
||||
@pytest.mark.xfail(
|
||||
"(env.MACOS and env.PYPY) or env.ANDROID",
|
||||
"(env.MACOS and env.PYPY) or env.ANDROID or env.FREEBSD",
|
||||
raises=RuntimeError,
|
||||
reason="See Issue #2847, PR #2999, PR #4324",
|
||||
reason="See Issue #2847, PR #2999, PR #4324, PR #5925",
|
||||
strict=not env.PYPY, # PR 5569
|
||||
)
|
||||
def test_cross_module_exception_translator():
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
// Inheritance test
|
||||
class TestFactory4 : public TestFactory3 {
|
||||
public:
|
||||
TestFactory4() : TestFactory3() { print_default_created(this); }
|
||||
TestFactory4() { print_default_created(this); }
|
||||
explicit TestFactory4(int v) : TestFactory3(v) { print_created(this, v); }
|
||||
~TestFactory4() override { print_destroyed(this); }
|
||||
};
|
||||
|
||||
@@ -288,11 +288,7 @@ def test_redirect_both(capfd):
|
||||
def test_threading():
|
||||
with m.ostream_redirect(stdout=True, stderr=False):
|
||||
# start some threads
|
||||
threads = []
|
||||
|
||||
# start some threads
|
||||
for _j in range(20):
|
||||
threads.append(m.TestThread())
|
||||
threads = [m.TestThread() for _j in range(20)]
|
||||
|
||||
# give the threads some time to fail
|
||||
threads[0].sleep()
|
||||
|
||||
@@ -458,7 +458,8 @@ def test_args_refcount():
|
||||
assert refcount(myval) == expected
|
||||
|
||||
exp3 = refcount(myval, myval, myval)
|
||||
assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3)
|
||||
# if we have to create a new tuple internally, then it will hold an extra reference for each item in it.
|
||||
assert m.args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3, exp3 + 3)
|
||||
assert refcount(myval) == expected
|
||||
|
||||
# This function takes the first arg as a `py::object` and the rest as a `py::args`. Unlike the
|
||||
|
||||
@@ -3,10 +3,18 @@ from __future__ import annotations
|
||||
import contextlib
|
||||
import os
|
||||
import pickle
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
import env
|
||||
import pybind11_tests
|
||||
|
||||
if env.IOS:
|
||||
pytest.skip("Subinterpreters not supported on iOS", allow_module_level=True)
|
||||
|
||||
# 3.14.0b3+, though sys.implementation.supports_isolated_interpreters is being added in b4
|
||||
# Can be simplified when we drop support for the first three betas
|
||||
CONCURRENT_INTERPRETERS_SUPPORT = (
|
||||
@@ -82,21 +90,23 @@ def get_interpreters(*, modern: bool):
|
||||
def test_independent_subinterpreters():
|
||||
"""Makes sure the internals object differs across independent subinterpreters"""
|
||||
|
||||
sys.path.append(".")
|
||||
sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
|
||||
|
||||
run_string, create = get_interpreters(modern=True)
|
||||
|
||||
m = pytest.importorskip("mod_per_interpreter_gil")
|
||||
import mod_per_interpreter_gil as m
|
||||
|
||||
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
|
||||
pytest.skip("Does not have subinterpreter support compiled in")
|
||||
|
||||
code = """
|
||||
import mod_per_interpreter_gil as m
|
||||
import pickle
|
||||
with open(pipeo, 'wb') as f:
|
||||
pickle.dump(m.internals_at(), f)
|
||||
"""
|
||||
code = textwrap.dedent(
|
||||
"""
|
||||
import mod_per_interpreter_gil as m
|
||||
import pickle
|
||||
with open(pipeo, 'wb') as f:
|
||||
pickle.dump(m.internals_at(), f)
|
||||
"""
|
||||
).strip()
|
||||
|
||||
with create() as interp1, create() as interp2:
|
||||
try:
|
||||
@@ -131,20 +141,22 @@ with open(pipeo, 'wb') as f:
|
||||
def test_independent_subinterpreters_modern():
|
||||
"""Makes sure the internals object differs across independent subinterpreters. Modern (3.14+) syntax."""
|
||||
|
||||
sys.path.append(".")
|
||||
sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
|
||||
|
||||
m = pytest.importorskip("mod_per_interpreter_gil")
|
||||
import mod_per_interpreter_gil as m
|
||||
|
||||
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
|
||||
pytest.skip("Does not have subinterpreter support compiled in")
|
||||
|
||||
from concurrent import interpreters
|
||||
|
||||
code = """
|
||||
import mod_per_interpreter_gil as m
|
||||
code = textwrap.dedent(
|
||||
"""
|
||||
import mod_per_interpreter_gil as m
|
||||
|
||||
values.put_nowait(m.internals_at())
|
||||
"""
|
||||
values.put_nowait(m.internals_at())
|
||||
"""
|
||||
).strip()
|
||||
|
||||
with contextlib.closing(interpreters.create()) as interp1, contextlib.closing(
|
||||
interpreters.create()
|
||||
@@ -175,21 +187,23 @@ values.put_nowait(m.internals_at())
|
||||
def test_dependent_subinterpreters():
|
||||
"""Makes sure the internals object differs across subinterpreters"""
|
||||
|
||||
sys.path.append(".")
|
||||
sys.path.insert(0, os.path.dirname(pybind11_tests.__file__))
|
||||
|
||||
run_string, create = get_interpreters(modern=False)
|
||||
|
||||
m = pytest.importorskip("mod_shared_interpreter_gil")
|
||||
import mod_shared_interpreter_gil as m
|
||||
|
||||
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
|
||||
pytest.skip("Does not have subinterpreter support compiled in")
|
||||
|
||||
code = """
|
||||
import mod_shared_interpreter_gil as m
|
||||
import pickle
|
||||
with open(pipeo, 'wb') as f:
|
||||
pickle.dump(m.internals_at(), f)
|
||||
"""
|
||||
code = textwrap.dedent(
|
||||
"""
|
||||
import mod_shared_interpreter_gil as m
|
||||
import pickle
|
||||
with open(pipeo, 'wb') as f:
|
||||
pickle.dump(m.internals_at(), f)
|
||||
"""
|
||||
).strip()
|
||||
|
||||
with create("legacy") as interp1:
|
||||
pipei, pipeo = os.pipe()
|
||||
@@ -198,3 +212,244 @@ with open(pipeo, 'wb') as f:
|
||||
res1 = pickle.load(f)
|
||||
|
||||
assert res1 != m.internals_at(), "internals should differ from main interpreter"
|
||||
|
||||
|
||||
PREAMBLE_CODE = textwrap.dedent(
|
||||
f"""
|
||||
def test():
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, {os.path.dirname(pybind11_tests.__file__)!r})
|
||||
|
||||
import collections
|
||||
import mod_per_interpreter_gil_with_singleton as m
|
||||
|
||||
objects = m.get_objects_in_singleton()
|
||||
expected = [
|
||||
type(None), # static type: shared between interpreters
|
||||
tuple, # static type: shared between interpreters
|
||||
list, # static type: shared between interpreters
|
||||
dict, # static type: shared between interpreters
|
||||
collections.OrderedDict, # static type: shared between interpreters
|
||||
collections.defaultdict, # heap type: dynamically created per interpreter
|
||||
collections.deque, # heap type: dynamically created per interpreter
|
||||
]
|
||||
# Check that we have the expected objects. Avoid IndexError by checking lengths first.
|
||||
assert len(objects) == len(expected), (
|
||||
f"Expected {{expected!r}} ({{len(expected)}}), got {{objects!r}} ({{len(objects)}})."
|
||||
)
|
||||
# The first ones are static types shared between interpreters.
|
||||
assert objects[:-2] == expected[:-2], (
|
||||
f"Expected static objects {{expected[:-2]!r}}, got {{objects[:-2]!r}}."
|
||||
)
|
||||
# The last two are heap types created per-interpreter.
|
||||
# The expected objects are dynamically imported from `collections`.
|
||||
assert objects[-2:] == expected[-2:], (
|
||||
f"Expected heap objects {{expected[-2:]!r}}, got {{objects[-2:]!r}}."
|
||||
)
|
||||
|
||||
assert hasattr(m, 'MyClass'), "Module missing MyClass"
|
||||
assert hasattr(m, 'MyGlobalError'), "Module missing MyGlobalError"
|
||||
assert hasattr(m, 'MyLocalError'), "Module missing MyLocalError"
|
||||
assert hasattr(m, 'MyEnum'), "Module missing MyEnum"
|
||||
"""
|
||||
).lstrip()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
|
||||
)
|
||||
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
|
||||
def test_import_module_with_singleton_per_interpreter():
|
||||
"""Tests that a singleton storing Python objects works correctly per-interpreter"""
|
||||
from concurrent import interpreters
|
||||
|
||||
code = f"{PREAMBLE_CODE.strip()}\n\ntest()\n"
|
||||
with contextlib.closing(interpreters.create()) as interp:
|
||||
interp.exec(code)
|
||||
|
||||
|
||||
def check_script_success_in_subprocess(code: str, *, rerun: int = 8) -> None:
|
||||
"""Runs the given code in a subprocess."""
|
||||
code = textwrap.dedent(code).strip()
|
||||
try:
|
||||
for _ in range(rerun): # run flakily failing test multiple times
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-c", code],
|
||||
cwd=os.getcwd(),
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as ex:
|
||||
raise RuntimeError(
|
||||
f"Subprocess failed with exit code {ex.returncode}.\n\n"
|
||||
f"Code:\n"
|
||||
f"```python\n"
|
||||
f"{code}\n"
|
||||
f"```\n\n"
|
||||
f"Output:\n"
|
||||
f"{ex.output}"
|
||||
) from None
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
|
||||
)
|
||||
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
|
||||
def test_import_in_subinterpreter_after_main():
|
||||
"""Tests that importing a module in a subinterpreter after the main interpreter works correctly"""
|
||||
check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
import contextlib
|
||||
import gc
|
||||
from concurrent import interpreters
|
||||
|
||||
test()
|
||||
|
||||
interp = None
|
||||
with contextlib.closing(interpreters.create()) as interp:
|
||||
interp.call(test)
|
||||
|
||||
del interp
|
||||
for _ in range(5):
|
||||
gc.collect()
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
import contextlib
|
||||
import gc
|
||||
import random
|
||||
from concurrent import interpreters
|
||||
|
||||
test()
|
||||
|
||||
interps = interp = None
|
||||
with contextlib.ExitStack() as stack:
|
||||
interps = [
|
||||
stack.enter_context(contextlib.closing(interpreters.create()))
|
||||
for _ in range(8)
|
||||
]
|
||||
random.shuffle(interps)
|
||||
for interp in interps:
|
||||
interp.call(test)
|
||||
|
||||
del interps, interp, stack
|
||||
for _ in range(5):
|
||||
gc.collect()
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
|
||||
)
|
||||
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
|
||||
def test_import_in_subinterpreter_before_main():
|
||||
"""Tests that importing a module in a subinterpreter before the main interpreter works correctly"""
|
||||
check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
import contextlib
|
||||
import gc
|
||||
from concurrent import interpreters
|
||||
|
||||
interp = None
|
||||
with contextlib.closing(interpreters.create()) as interp:
|
||||
interp.call(test)
|
||||
|
||||
test()
|
||||
|
||||
del interp
|
||||
for _ in range(5):
|
||||
gc.collect()
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
import contextlib
|
||||
import gc
|
||||
from concurrent import interpreters
|
||||
|
||||
interps = interp = None
|
||||
with contextlib.ExitStack() as stack:
|
||||
interps = [
|
||||
stack.enter_context(contextlib.closing(interpreters.create()))
|
||||
for _ in range(8)
|
||||
]
|
||||
for interp in interps:
|
||||
interp.call(test)
|
||||
|
||||
test()
|
||||
|
||||
del interps, interp, stack
|
||||
for _ in range(5):
|
||||
gc.collect()
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
import contextlib
|
||||
import gc
|
||||
from concurrent import interpreters
|
||||
|
||||
interps = interp = None
|
||||
with contextlib.ExitStack() as stack:
|
||||
interps = [
|
||||
stack.enter_context(contextlib.closing(interpreters.create()))
|
||||
for _ in range(8)
|
||||
]
|
||||
for interp in interps:
|
||||
interp.call(test)
|
||||
|
||||
test()
|
||||
|
||||
del interps, interp, stack
|
||||
for _ in range(5):
|
||||
gc.collect()
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
|
||||
)
|
||||
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
|
||||
def test_import_in_subinterpreter_concurrently():
|
||||
"""Tests that importing a module in multiple subinterpreters concurrently works correctly"""
|
||||
check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
import gc
|
||||
from concurrent.futures import InterpreterPoolExecutor, as_completed
|
||||
|
||||
futures = future = None
|
||||
with InterpreterPoolExecutor(max_workers=16) as executor:
|
||||
futures = [executor.submit(test) for _ in range(32)]
|
||||
for future in as_completed(futures):
|
||||
future.result()
|
||||
del futures, future, executor
|
||||
|
||||
for _ in range(5):
|
||||
gc.collect()
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
// Size / dtype checks.
|
||||
struct DtypeCheck {
|
||||
py::dtype numpy{};
|
||||
py::dtype pybind11{};
|
||||
py::dtype numpy;
|
||||
py::dtype pybind11;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@@ -43,11 +43,11 @@ std::vector<DtypeCheck> get_concrete_dtype_checks() {
|
||||
}
|
||||
|
||||
struct DtypeSizeCheck {
|
||||
std::string name{};
|
||||
std::string name;
|
||||
int size_cpp{};
|
||||
int size_numpy{};
|
||||
// For debugging.
|
||||
py::dtype dtype{};
|
||||
py::dtype dtype;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -1211,4 +1211,6 @@ TEST_SUBMODULE(pytypes, m) {
|
||||
m.def("check_type_is", [](const py::object &x) -> py::typing::TypeIs<RealNumber> {
|
||||
return py::isinstance<RealNumber>(x);
|
||||
});
|
||||
|
||||
m.def("const_kwargs_ref_to_str", [](const py::kwargs &kwargs) { return py::str(kwargs); });
|
||||
}
|
||||
|
||||
@@ -1367,3 +1367,8 @@ def test_arg_return_type_hints(doc, backport_typehints):
|
||||
backport_typehints(doc(m.check_type_guard))
|
||||
== "check_type_guard(arg0: list[object]) -> typing.TypeGuard[list[float]]"
|
||||
)
|
||||
|
||||
|
||||
def test_const_kwargs_ref_to_str():
|
||||
assert m.const_kwargs_ref_to_str() == "{}"
|
||||
assert m.const_kwargs_ref_to_str(a=1) == "{'a': 1}"
|
||||
|
||||
@@ -556,7 +556,7 @@ TEST_SUBMODULE(sequences_and_iterators, m) {
|
||||
});
|
||||
|
||||
m.def("count_nonzeros", [](const py::dict &d) {
|
||||
return std::count_if(d.begin(), d.end(), [](std::pair<py::handle, py::handle> p) {
|
||||
return std::count_if(d.begin(), d.end(), [](const std::pair<py::handle, py::handle> &p) {
|
||||
return p.second.cast<int>() != 0;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -220,7 +220,7 @@ struct SharedPtrRef {
|
||||
~A() { print_destroyed(this); }
|
||||
};
|
||||
|
||||
A value = {};
|
||||
A value;
|
||||
std::shared_ptr<A> shared = std::make_shared<A>();
|
||||
};
|
||||
|
||||
@@ -228,13 +228,14 @@ struct SharedPtrRef {
|
||||
struct SharedFromThisRef {
|
||||
struct B : std::enable_shared_from_this<B> {
|
||||
B() { print_created(this); }
|
||||
// NOLINTNEXTLINE(bugprone-copy-constructor-init)
|
||||
// NOLINTNEXTLINE(bugprone-copy-constructor-init, readability-redundant-member-init)
|
||||
B(const B &) : std::enable_shared_from_this<B>() { print_copy_created(this); }
|
||||
// NOLINTNEXTLINE(readability-redundant-member-init)
|
||||
B(B &&) noexcept : std::enable_shared_from_this<B>() { print_move_created(this); }
|
||||
~B() { print_destroyed(this); }
|
||||
};
|
||||
|
||||
B value = {};
|
||||
B value;
|
||||
std::shared_ptr<B> shared = std::make_shared<B>();
|
||||
};
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ template <class Map>
|
||||
Map *times_ten(int n) {
|
||||
auto *m = new Map();
|
||||
for (int i = 1; i <= n; i++) {
|
||||
m->emplace(int(i), E_nc(10 * i));
|
||||
m->emplace(i, E_nc(10 * i));
|
||||
}
|
||||
return m;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ NestMap *times_hundred(int n) {
|
||||
auto *m = new NestMap();
|
||||
for (int i = 1; i <= n; i++) {
|
||||
for (int j = 1; j <= n; j++) {
|
||||
(*m)[i].emplace(int(j * 10), E_nc(100 * j));
|
||||
(*m)[i].emplace(j * 10, E_nc(100 * j));
|
||||
}
|
||||
}
|
||||
return m;
|
||||
|
||||
@@ -258,7 +258,7 @@ def test_noncopyable_containers():
|
||||
assert nvnc[i][j].value == j + 1
|
||||
|
||||
# Note: maps do not have .values()
|
||||
for _, v in nvnc.items():
|
||||
for v in nvnc.values():
|
||||
for i, j in enumerate(v, start=1):
|
||||
assert j.value == i
|
||||
|
||||
@@ -269,7 +269,7 @@ def test_noncopyable_containers():
|
||||
assert nmnc[i][j].value == 10 * j
|
||||
|
||||
vsum = 0
|
||||
for _, v_o in nmnc.items():
|
||||
for v_o in nmnc.values():
|
||||
for k_i, v_i in v_o.items():
|
||||
assert v_i.value == 10 * k_i
|
||||
vsum += v_i.value
|
||||
@@ -283,7 +283,7 @@ def test_noncopyable_containers():
|
||||
assert numnc[i][j].value == 10 * j
|
||||
|
||||
vsum = 0
|
||||
for _, v_o in numnc.items():
|
||||
for v_o in numnc.values():
|
||||
for k_i, v_i in v_o.items():
|
||||
assert v_i.value == 10 * k_i
|
||||
vsum += v_i.value
|
||||
|
||||
@@ -5,7 +5,10 @@ import pytest
|
||||
from pybind11_tests import unnamed_namespace_a as m
|
||||
from pybind11_tests import unnamed_namespace_b as mb
|
||||
|
||||
XFAIL_CONDITION = "not m.defined_WIN32_or__WIN32 and (m.defined___clang__ or m.defined__LIBCPP_VERSION)"
|
||||
XFAIL_CONDITION = (
|
||||
"m.defined__LIBCPP_VERSION or "
|
||||
"(not m.defined_WIN32_or__WIN32 and m.defined___clang__)"
|
||||
)
|
||||
XFAIL_REASON = "Known issues: https://github.com/pybind/pybind11/pull/4319"
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,9 @@ add_custom_target(
|
||||
cpptest
|
||||
COMMAND "$<TARGET_FILE:test_with_catch>"
|
||||
DEPENDS test_with_catch
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
USES_TERMINAL # Ensures output is shown immediately (not buffered and possibly lost on crash)
|
||||
)
|
||||
|
||||
pybind11_add_module(external_module THIN_LTO external_module.cpp)
|
||||
set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
|
||||
@@ -3,6 +3,17 @@
|
||||
|
||||
#include <pybind11/embed.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
|
||||
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
|
||||
PYBIND11_WARNING_DISABLE_MSVC(4996)
|
||||
@@ -13,11 +24,126 @@ PYBIND11_WARNING_DISABLE_MSVC(4996)
|
||||
#endif
|
||||
|
||||
#define CATCH_CONFIG_RUNNER
|
||||
#define CATCH_CONFIG_DEFAULT_REPORTER "progress"
|
||||
#include "catch_skip.h"
|
||||
|
||||
#include <catch.hpp>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
// Simple progress reporter that prints a line per test case.
|
||||
namespace {
|
||||
|
||||
class ProgressReporter : public Catch::StreamingReporterBase<ProgressReporter> {
|
||||
public:
|
||||
using StreamingReporterBase<ProgressReporter>::StreamingReporterBase;
|
||||
|
||||
static std::string getDescription() { return "Simple progress reporter (one line per test)"; }
|
||||
|
||||
void testCaseStarting(Catch::TestCaseInfo const &testInfo) override {
|
||||
print_python_version_once();
|
||||
auto &os = Catch::cout();
|
||||
os << "[ RUN ] " << testInfo.name << '\n';
|
||||
os.flush();
|
||||
}
|
||||
|
||||
void testCaseEnded(Catch::TestCaseStats const &stats) override {
|
||||
bool failed = stats.totals.assertions.failed > 0;
|
||||
auto &os = Catch::cout();
|
||||
os << (failed ? "[ FAILED ] " : "[ OK ] ") << stats.testInfo.name << '\n';
|
||||
os.flush();
|
||||
}
|
||||
|
||||
void noMatchingTestCases(std::string const &spec) override {
|
||||
auto &os = Catch::cout();
|
||||
os << "[ NO TEST ] no matching test cases for spec: " << spec << '\n';
|
||||
os.flush();
|
||||
}
|
||||
|
||||
void reportInvalidArguments(std::string const &arg) override {
|
||||
auto &os = Catch::cout();
|
||||
os << "[ ERROR ] invalid Catch2 arguments: " << arg << '\n';
|
||||
os.flush();
|
||||
}
|
||||
|
||||
void assertionStarting(Catch::AssertionInfo const &) override {}
|
||||
|
||||
bool assertionEnded(Catch::AssertionStats const &) override { return false; }
|
||||
|
||||
void testRunEnded(Catch::TestRunStats const &stats) override {
|
||||
auto &os = Catch::cout();
|
||||
auto passed = stats.totals.testCases.passed;
|
||||
auto failed = stats.totals.testCases.failed;
|
||||
auto total = passed + failed;
|
||||
auto assertions = stats.totals.assertions.passed + stats.totals.assertions.failed;
|
||||
if (failed == 0) {
|
||||
os << "[ PASSED ] " << total << " test cases, " << assertions << " assertions.\n";
|
||||
} else {
|
||||
os << "[ FAILED ] " << failed << " of " << total << " test cases, " << assertions
|
||||
<< " assertions.\n";
|
||||
}
|
||||
os.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
void print_python_version_once() {
|
||||
if (printed_) {
|
||||
return;
|
||||
}
|
||||
printed_ = true;
|
||||
auto &os = Catch::cout();
|
||||
os << "[ PYTHON ] " << Py_GetVersion() << '\n';
|
||||
os.flush();
|
||||
}
|
||||
|
||||
bool printed_ = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
CATCH_REGISTER_REPORTER("progress", ProgressReporter)
|
||||
|
||||
namespace {
|
||||
|
||||
std::string get_utc_timestamp() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t_now = std::chrono::system_clock::to_time_t(now);
|
||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
|
||||
|
||||
std::tm utc_tm{};
|
||||
#if defined(_WIN32)
|
||||
gmtime_s(&utc_tm, &time_t_now);
|
||||
#else
|
||||
gmtime_r(&time_t_now, &utc_tm);
|
||||
#endif
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(&utc_tm, "%Y-%m-%d %H:%M:%S") << '.' << std::setfill('0') << std::setw(3)
|
||||
<< ms.count() << 'Z';
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// Signal handler to print a message when the process is terminated.
|
||||
// Uses only async-signal-safe functions.
|
||||
void termination_signal_handler(int sig) {
|
||||
const char *msg = "[ SIGNAL ] Process received SIGTERM\n";
|
||||
// write() is async-signal-safe, unlike std::cout
|
||||
ssize_t written = write(STDOUT_FILENO, msg, strlen(msg));
|
||||
(void) written; // suppress "unused variable" warnings
|
||||
// Re-raise with default handler to get proper exit status
|
||||
std::signal(sig, SIG_DFL);
|
||||
std::raise(sig);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
#ifndef _WIN32
|
||||
std::signal(SIGTERM, termination_signal_handler);
|
||||
#endif
|
||||
|
||||
// Setup for TEST_CASE in test_interpreter.cpp, tagging on a large random number:
|
||||
std::string updated_pythonpath("pybind11_test_with_catch_PYTHONPATH_2099743835476552");
|
||||
const char *preexisting_pythonpath = getenv("PYTHONPATH");
|
||||
@@ -35,9 +161,15 @@ int main(int argc, char *argv[]) {
|
||||
setenv("PYTHONPATH", updated_pythonpath.c_str(), /*replace=*/1);
|
||||
#endif
|
||||
|
||||
std::cout << "[ STARTING ] " << get_utc_timestamp() << '\n';
|
||||
std::cout.flush();
|
||||
|
||||
py::scoped_interpreter guard{};
|
||||
|
||||
auto result = Catch::Session().run(argc, argv);
|
||||
|
||||
std::cout << "[ DONE ] " << get_utc_timestamp() << " (result " << result << ")\n";
|
||||
std::cout.flush();
|
||||
|
||||
return result < 0xff ? result : 0xff;
|
||||
}
|
||||
|
||||
16
tests/test_with_catch/catch_skip.h
Normal file
16
tests/test_with_catch/catch_skip.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Macro to skip a test at runtime with a visible message.
|
||||
// Catch2 v2 doesn't have native skip support (v3 does with SKIP()).
|
||||
// The test will count as "passed" in totals, but the output clearly shows it was skipped.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <catch.hpp>
|
||||
|
||||
#define PYBIND11_CATCH2_SKIP_IF(condition, reason) \
|
||||
do { \
|
||||
if (condition) { \
|
||||
Catch::cout() << "[ SKIPPED ] " << (reason) << '\n'; \
|
||||
Catch::cout().flush(); \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
@@ -1,11 +1,14 @@
|
||||
#include <pybind11/embed.h>
|
||||
#ifdef PYBIND11_HAS_SUBINTERPRETER_SUPPORT
|
||||
# include <pybind11/gil_safe_call_once.h>
|
||||
# include <pybind11/subinterpreter.h>
|
||||
|
||||
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
|
||||
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
|
||||
PYBIND11_WARNING_DISABLE_MSVC(4996)
|
||||
|
||||
# include "catch_skip.h"
|
||||
|
||||
# include <catch.hpp>
|
||||
# include <cstdlib>
|
||||
# include <fstream>
|
||||
@@ -28,7 +31,7 @@ void unsafe_reset_internals_for_single_interpreter() {
|
||||
py::detail::get_local_internals_pp_manager().unref();
|
||||
|
||||
// we know there are no other interpreters, so we can lower this. SUPER DANGEROUS
|
||||
py::detail::get_num_interpreters_seen() = 1;
|
||||
py::detail::has_seen_non_main_interpreter() = false;
|
||||
|
||||
// now we unref the static global singleton internals
|
||||
py::detail::get_internals_pp_manager().unref();
|
||||
@@ -39,6 +42,30 @@ void unsafe_reset_internals_for_single_interpreter() {
|
||||
py::detail::get_local_internals();
|
||||
}
|
||||
|
||||
py::object &get_dict_type_object() {
|
||||
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
|
||||
return storage
|
||||
.call_once_and_store_result(
|
||||
[]() -> py::object { return py::module_::import("builtins").attr("dict"); })
|
||||
.get_stored();
|
||||
}
|
||||
|
||||
py::object &get_ordered_dict_type_object() {
|
||||
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
|
||||
return storage
|
||||
.call_once_and_store_result(
|
||||
[]() -> py::object { return py::module_::import("collections").attr("OrderedDict"); })
|
||||
.get_stored();
|
||||
}
|
||||
|
||||
py::object &get_default_dict_type_object() {
|
||||
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
|
||||
return storage
|
||||
.call_once_and_store_result(
|
||||
[]() -> py::object { return py::module_::import("collections").attr("defaultdict"); })
|
||||
.get_stored();
|
||||
}
|
||||
|
||||
TEST_CASE("Single Subinterpreter") {
|
||||
unsafe_reset_internals_for_single_interpreter();
|
||||
|
||||
@@ -104,14 +131,21 @@ TEST_CASE("Move Subinterpreter") {
|
||||
py::module_::import("external_module");
|
||||
}
|
||||
|
||||
std::thread([&]() {
|
||||
auto t = std::thread([&]() {
|
||||
// Use it again
|
||||
{
|
||||
py::subinterpreter_scoped_activate activate(*sub);
|
||||
py::module_::import("external_module");
|
||||
}
|
||||
sub.reset();
|
||||
}).join();
|
||||
});
|
||||
|
||||
// on 3.14.1+ destructing a sub-interpreter does a stop-the-world. we need to detach our
|
||||
// thread state in order for that to be possible.
|
||||
{
|
||||
py::gil_scoped_release nogil;
|
||||
t.join();
|
||||
}
|
||||
|
||||
REQUIRE(!sub);
|
||||
|
||||
@@ -299,6 +333,103 @@ TEST_CASE("Multiple Subinterpreters") {
|
||||
unsafe_reset_internals_for_single_interpreter();
|
||||
}
|
||||
|
||||
// Test that gil_safe_call_once_and_store provides per-interpreter storage.
|
||||
// Without the per-interpreter storage fix, the subinterpreter would see the value
|
||||
// cached by the main interpreter, which is invalid (different interpreter's object).
|
||||
TEST_CASE("gil_safe_call_once_and_store per-interpreter isolation") {
|
||||
unsafe_reset_internals_for_single_interpreter();
|
||||
|
||||
// This static simulates a typical usage pattern where a module caches
|
||||
// an imported object using gil_safe_call_once_and_store.
|
||||
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> storage;
|
||||
|
||||
// Get the interpreter ID in the main interpreter
|
||||
auto main_interp_id = PyInterpreterState_GetID(PyInterpreterState_Get());
|
||||
|
||||
// Store a value in the main interpreter - we'll store the interpreter ID as a Python int
|
||||
auto &main_value = storage
|
||||
.call_once_and_store_result([]() {
|
||||
return py::int_(PyInterpreterState_GetID(PyInterpreterState_Get()));
|
||||
})
|
||||
.get_stored();
|
||||
REQUIRE(main_value.cast<int64_t>() == main_interp_id);
|
||||
|
||||
py::object dict_type = get_dict_type_object();
|
||||
py::object ordered_dict_type = get_ordered_dict_type_object();
|
||||
py::object default_dict_type = get_default_dict_type_object();
|
||||
|
||||
int64_t sub_interp_id = -1;
|
||||
int64_t sub_cached_value = -1;
|
||||
|
||||
bool sub_default_dict_type_destroyed = false;
|
||||
|
||||
// Create a subinterpreter and check that it gets its own storage
|
||||
{
|
||||
py::scoped_subinterpreter ssi;
|
||||
|
||||
sub_interp_id = PyInterpreterState_GetID(PyInterpreterState_Get());
|
||||
REQUIRE(sub_interp_id != main_interp_id);
|
||||
|
||||
// Access the same static storage from the subinterpreter.
|
||||
// With per-interpreter storage, this should call the lambda again
|
||||
// and cache a NEW value for this interpreter.
|
||||
// Without per-interpreter storage, this would return main's cached value.
|
||||
auto &sub_value
|
||||
= storage
|
||||
.call_once_and_store_result([]() {
|
||||
return py::int_(PyInterpreterState_GetID(PyInterpreterState_Get()));
|
||||
})
|
||||
.get_stored();
|
||||
|
||||
sub_cached_value = sub_value.cast<int64_t>();
|
||||
|
||||
// The cached value should be the SUBINTERPRETER's ID, not the main interpreter's.
|
||||
// This would fail without per-interpreter storage.
|
||||
REQUIRE(sub_cached_value == sub_interp_id);
|
||||
REQUIRE(sub_cached_value != main_interp_id);
|
||||
|
||||
py::object sub_dict_type = get_dict_type_object();
|
||||
py::object sub_ordered_dict_type = get_ordered_dict_type_object();
|
||||
py::object sub_default_dict_type = get_default_dict_type_object();
|
||||
|
||||
// Verify that the subinterpreter has its own cached type objects.
|
||||
// For static types, they should be the same object across interpreters.
|
||||
// See also: https://docs.python.org/3/c-api/typeobj.html#static-types
|
||||
REQUIRE(sub_dict_type.is(dict_type)); // dict is a static type
|
||||
REQUIRE(sub_ordered_dict_type.is(ordered_dict_type)); // OrderedDict is a static type
|
||||
// For heap types, they are dynamically created per-interpreter.
|
||||
// See also: https://docs.python.org/3/c-api/typeobj.html#heap-types
|
||||
REQUIRE_FALSE(sub_default_dict_type.is(default_dict_type)); // defaultdict is a heap type
|
||||
|
||||
// Set up a weakref callback to detect when the subinterpreter's cached default_dict_type
|
||||
// is destroyed so the gil_safe_call_once_and_store storage is not leaked when the
|
||||
// subinterpreter is shutdown.
|
||||
(void) py::weakref(sub_default_dict_type,
|
||||
py::cpp_function([&](py::handle weakref) -> void {
|
||||
sub_default_dict_type_destroyed = true;
|
||||
weakref.dec_ref();
|
||||
}))
|
||||
.release();
|
||||
}
|
||||
|
||||
// Back in main interpreter, verify main's value is unchanged
|
||||
auto &main_value_after = storage.get_stored();
|
||||
REQUIRE(main_value_after.cast<int64_t>() == main_interp_id);
|
||||
|
||||
// Verify that the types cached in main are unchanged
|
||||
py::object dict_type_after = get_dict_type_object();
|
||||
py::object ordered_dict_type_after = get_ordered_dict_type_object();
|
||||
py::object default_dict_type_after = get_default_dict_type_object();
|
||||
REQUIRE(dict_type_after.is(dict_type));
|
||||
REQUIRE(ordered_dict_type_after.is(ordered_dict_type));
|
||||
REQUIRE(default_dict_type_after.is(default_dict_type));
|
||||
|
||||
// Verify that the subinterpreter's cached default_dict_type was destroyed
|
||||
REQUIRE(sub_default_dict_type_destroyed);
|
||||
|
||||
unsafe_reset_internals_for_single_interpreter();
|
||||
}
|
||||
|
||||
# ifdef Py_MOD_PER_INTERPRETER_GIL_SUPPORTED
|
||||
TEST_CASE("Per-Subinterpreter GIL") {
|
||||
auto main_int
|
||||
|
||||
@@ -41,7 +41,18 @@ set(pybind11_INCLUDE_DIRS
|
||||
"${pybind11_INCLUDE_DIR}"
|
||||
CACHE INTERNAL "Include directory for pybind11 (Python not requested)")
|
||||
|
||||
if(CMAKE_CROSSCOMPILING AND PYBIND11_USE_CROSSCOMPILING)
|
||||
# CMP0190 prohibits calling FindPython with both Interpreter and Development components
|
||||
# when cross-compiling, unless the CMAKE_CROSSCOMPILING_EMULATOR variable is defined.
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.1")
|
||||
cmake_policy(GET CMP0190 _pybind11_cmp0190)
|
||||
if(_pybind11_cmp0190 STREQUAL "NEW")
|
||||
set(PYBIND11_USE_CROSSCOMPILING "ON")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CROSSCOMPILING
|
||||
AND PYBIND11_USE_CROSSCOMPILING
|
||||
AND NOT DEFINED CMAKE_CROSSCOMPILING_EMULATOR)
|
||||
set(_PYBIND11_CROSSCOMPILING
|
||||
ON
|
||||
CACHE INTERNAL "")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
function(pybind11_guess_python_module_extension python)
|
||||
|
||||
@@ -14,15 +14,23 @@ function(pybind11_guess_python_module_extension python)
|
||||
STRING
|
||||
"Extension suffix for Python extension modules (Initialized from SETUPTOOLS_EXT_SUFFIX)")
|
||||
endif()
|
||||
|
||||
# The final extension depends on the system
|
||||
set(_PY_BUILD_EXTENSION "${CMAKE_SHARED_MODULE_SUFFIX}")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(_PY_BUILD_EXTENSION ".pyd")
|
||||
endif()
|
||||
|
||||
# If running under scikit-build-core, use the SKBUILD_SOABI variable:
|
||||
if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX AND DEFINED SKBUILD_SOABI)
|
||||
message(STATUS "Determining Python extension suffix based on SKBUILD_SOABI: ${SKBUILD_SOABI}")
|
||||
set(PYTHON_MODULE_EXT_SUFFIX ".${SKBUILD_SOABI}${_PY_BUILD_EXTENSION}")
|
||||
endif()
|
||||
|
||||
# If that didn't work, use the Python_SOABI variable:
|
||||
if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX AND DEFINED ${python}_SOABI)
|
||||
message(
|
||||
STATUS "Determining Python extension suffix based on ${python}_SOABI: ${${python}_SOABI}")
|
||||
# The final extension depends on the system
|
||||
set(_PY_BUILD_EXTENSION "${CMAKE_SHARED_MODULE_SUFFIX}")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(_PY_BUILD_EXTENSION ".pyd")
|
||||
endif()
|
||||
# If the SOABI already has an extension, use it as the full suffix
|
||||
# (used for debug versions of Python on Windows)
|
||||
if(${python}_SOABI MATCHES "\\.")
|
||||
@@ -43,9 +51,9 @@ function(pybind11_guess_python_module_extension python)
|
||||
|
||||
# If we could not deduce the extension suffix, unset the results:
|
||||
if(NOT DEFINED PYTHON_MODULE_EXT_SUFFIX)
|
||||
unset(PYTHON_MODULE_DEBUG_POSTFIX PARENT_SCOPE)
|
||||
unset(PYTHON_MODULE_EXTENSION PARENT_SCOPE)
|
||||
unset(PYTHON_IS_DEBUG PARENT_SCOPE)
|
||||
unset(PYTHON_MODULE_DEBUG_POSTFIX CACHE)
|
||||
unset(PYTHON_MODULE_EXTENSION CACHE)
|
||||
unset(PYTHON_IS_DEBUG CACHE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -75,12 +83,12 @@ function(pybind11_guess_python_module_extension python)
|
||||
# Return results
|
||||
set(PYTHON_MODULE_DEBUG_POSTFIX
|
||||
"${_PYTHON_MODULE_DEBUG_POSTFIX}"
|
||||
PARENT_SCOPE)
|
||||
CACHE INTERNAL "")
|
||||
set(PYTHON_MODULE_EXTENSION
|
||||
"${_PYTHON_MODULE_EXTENSION}"
|
||||
PARENT_SCOPE)
|
||||
CACHE INTERNAL "")
|
||||
set(PYTHON_IS_DEBUG
|
||||
"${_PYTHON_IS_DEBUG}"
|
||||
PARENT_SCOPE)
|
||||
CACHE INTERNAL "")
|
||||
|
||||
endfunction()
|
||||
|
||||
@@ -106,18 +106,7 @@ if(PYBIND11_MASTER_PROJECT)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT _PYBIND11_CROSSCOMPILING)
|
||||
# If a user finds Python, they may forget to include the Interpreter component
|
||||
# and the following two steps require it. It is highly recommended by CMake
|
||||
# when finding development libraries anyway, so we will require it.
|
||||
if(NOT DEFINED ${_Python}_EXECUTABLE)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"${_Python} was found without the Interpreter component. Pybind11 requires this component."
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
if(NOT _PYBIND11_CROSSCOMPILING AND DEFINED ${_Python}_EXECUTABLE)
|
||||
if(DEFINED PYBIND11_PYTHON_EXECUTABLE_LAST AND NOT ${_Python}_EXECUTABLE STREQUAL
|
||||
PYBIND11_PYTHON_EXECUTABLE_LAST)
|
||||
# Detect changes to the Python version/binary in subsequent CMake runs, and refresh config if needed
|
||||
@@ -190,15 +179,15 @@ else()
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/pybind11GuessPythonExtSuffix.cmake")
|
||||
pybind11_guess_python_module_extension("${_Python}")
|
||||
endif()
|
||||
# When cross-compiling, we cannot query the Python interpreter, so we require
|
||||
# the user to set these variables explicitly.
|
||||
if(NOT DEFINED PYTHON_IS_DEBUG
|
||||
OR NOT DEFINED PYTHON_MODULE_EXTENSION
|
||||
OR NOT DEFINED PYTHON_MODULE_DEBUG_POSTFIX)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"When cross-compiling, you should set the PYTHON_IS_DEBUG, PYTHON_MODULE_EXTENSION and PYTHON_MODULE_DEBUG_POSTFIX \
|
||||
variables appropriately before loading pybind11 (e.g. in your CMake toolchain file)")
|
||||
"A Python interpreter was not found, or you are cross-compiling, and the "
|
||||
"PYTHON_IS_DEBUG, PYTHON_MODULE_EXTENSION and PYTHON_MODULE_DEBUG_POSTFIX "
|
||||
"variables could not be guessed. Set these variables appropriately before "
|
||||
"loading pybind11 (e.g. in your CMake toolchain file)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -248,10 +237,7 @@ if(TARGET ${_Python}::Module)
|
||||
# files.
|
||||
get_target_property(module_target_type ${_Python}::Module TYPE)
|
||||
if(ANDROID AND module_target_type STREQUAL INTERFACE_LIBRARY)
|
||||
set_property(
|
||||
TARGET ${_Python}::Module
|
||||
APPEND
|
||||
PROPERTY INTERFACE_LINK_LIBRARIES "${${_Python}_LIBRARIES}")
|
||||
target_link_libraries(${_Python}::Module INTERFACE ${${_Python}_LIBRARIES})
|
||||
endif()
|
||||
|
||||
set_property(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.15...4.0)
|
||||
cmake_minimum_required(VERSION 3.15...4.2)
|
||||
|
||||
# Tests for pybind11_guess_python_module_extension
|
||||
# Run using `cmake -P tools/test-pybind11GuessPythonExtSuffix.cmake`
|
||||
@@ -87,6 +87,30 @@ unset(PYTHON_MODULE_EXT_SUFFIX)
|
||||
unset(PYTHON_MODULE_EXT_SUFFIX CACHE)
|
||||
unset(ENV{SETUPTOOLS_EXT_SUFFIX})
|
||||
|
||||
# Check the priority of the possible suffix sources.
|
||||
set(ENV{SETUPTOOLS_EXT_SUFFIX} ".from-setuptools.pyd")
|
||||
set(SKBUILD_SOABI "from-skbuild")
|
||||
set(Python3_SOABI "from-python3")
|
||||
pybind11_guess_python_module_extension("Python3")
|
||||
expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-setuptools.pyd")
|
||||
|
||||
unset(PYTHON_MODULE_EXT_SUFFIX CACHE)
|
||||
unset(ENV{SETUPTOOLS_EXT_SUFFIX})
|
||||
pybind11_guess_python_module_extension("Python3")
|
||||
expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-skbuild.pyd")
|
||||
|
||||
unset(SKBUILD_SOABI)
|
||||
pybind11_guess_python_module_extension("Python3")
|
||||
expect_streq("${PYTHON_MODULE_EXTENSION}" ".from-python3.pyd")
|
||||
|
||||
set(Python3_SOABI "")
|
||||
pybind11_guess_python_module_extension("Python3")
|
||||
expect_streq("${PYTHON_MODULE_EXTENSION}" ".pyd")
|
||||
|
||||
unset(Python3_SOABI)
|
||||
pybind11_guess_python_module_extension("Python3")
|
||||
expect_streq("${PYTHON_MODULE_EXTENSION}" "")
|
||||
|
||||
# macOS
|
||||
set(CMAKE_SYSTEM_NAME "Darwin")
|
||||
set(CMAKE_SHARED_MODULE_SUFFIX ".so")
|
||||
|
||||
Reference in New Issue
Block a user