update pybind11

This commit is contained in:
Thomas Kunze
2024-06-29 16:50:08 +02:00
249 changed files with 31681 additions and 11123 deletions

View File

@@ -1,12 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from ._version import version_info, __version__
from .commands import get_include, get_cmake_dir
import sys
if sys.version_info < (3, 7): # noqa: UP036
msg = "pybind11 does not support Python < 3.7. v2.12 was the last release supporting Python 3.6."
raise ImportError(msg)
from ._version import __version__, version_info
from .commands import get_cmake_dir, get_include, get_pkgconfig_dir
__all__ = (
"version_info",
"__version__",
"get_include",
"get_cmake_dir",
"get_pkgconfig_dir",
)

View File

@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import print_function
# pylint: disable=missing-function-docstring
from __future__ import annotations
import argparse
import sys
import sysconfig
from .commands import get_include, get_cmake_dir
from ._version import __version__
from .commands import get_cmake_dir, get_include, get_pkgconfig_dir
def print_includes():
# type: () -> None
def print_includes() -> None:
dirs = [
sysconfig.get_path("include"),
sysconfig.get_path("platinclude"),
@@ -25,10 +25,14 @@ def print_includes():
print(" ".join("-I" + d for d in unique_dirs))
def main():
# type: () -> None
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"--version",
action="version",
version=__version__,
help="Print the version and exit.",
)
parser.add_argument(
"--includes",
action="store_true",
@@ -39,6 +43,11 @@ def main():
action="store_true",
help="Print the CMake module directory, ideal for setting -Dpybind11_ROOT in CMake.",
)
parser.add_argument(
"--pkgconfigdir",
action="store_true",
help="Print the pkgconfig directory, ideal for setting $PKG_CONFIG_PATH.",
)
args = parser.parse_args()
if not sys.argv[1:]:
parser.print_help()
@@ -46,6 +55,8 @@ def main():
print_includes()
if args.cmakedir:
print(get_cmake_dir())
if args.pkgconfigdir:
print(get_pkgconfig_dir())
if __name__ == "__main__":

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
def _to_int(s):
def _to_int(s: str) -> int | str:
try:
return int(s)
except ValueError:
return s
__version__ = "2.6.0"
__version__ = "2.13.1"
version_info = tuple(_to_int(s) for s in __version__.split("."))

View File

@@ -1,6 +0,0 @@
from typing import Union, Tuple
def _to_int(s: str) -> Union[int, str]: ...
__version__: str
version_info: Tuple[Union[int, str], ...]

View File

@@ -1,22 +1,39 @@
# -*- coding: utf-8 -*-
import os
from __future__ import annotations
import os
DIR = os.path.abspath(os.path.dirname(__file__))
def get_include(user=False):
# type: (bool) -> str
def get_include(user: bool = False) -> str: # noqa: ARG001
"""
Return the path to the pybind11 include directory. The historical "user"
argument is unused, and may be removed.
"""
installed_path = os.path.join(DIR, "include")
source_path = os.path.join(os.path.dirname(DIR), "include")
return installed_path if os.path.exists(installed_path) else source_path
def get_cmake_dir():
# type: () -> str
def get_cmake_dir() -> str:
"""
Return the path to the pybind11 CMake module directory.
"""
cmake_installed_path = os.path.join(DIR, "share", "cmake", "pybind11")
if os.path.exists(cmake_installed_path):
return cmake_installed_path
else:
msg = "pybind11 not installed, installation required to access the CMake files"
raise ImportError(msg)
msg = "pybind11 not installed, installation required to access the CMake files"
raise ImportError(msg)
def get_pkgconfig_dir() -> str:
"""
Return the path to the pybind11 pkgconfig directory.
"""
pkgconfig_installed_path = os.path.join(DIR, "share", "pkgconfig")
if os.path.exists(pkgconfig_installed_path):
return pkgconfig_installed_path
msg = "pybind11 not installed, installation required to access the pkgconfig files"
raise ImportError(msg)

View File

@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
"""
This module provides helpers for C++11+ projects using pybind11.
@@ -38,28 +36,45 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# If you copy this file in, you don't
# need the .pyi file; it's just an interface file for static type checkers.
from __future__ import annotations
import contextlib
import os
import platform
import shlex
import shutil
import sys
import sysconfig
import tempfile
import threading
import warnings
from functools import lru_cache
from pathlib import Path
from typing import (
Any,
Callable,
Iterable,
Iterator,
List,
Optional,
Tuple,
TypeVar,
Union,
)
try:
from setuptools.command.build_ext import build_ext as _build_ext
from setuptools import Extension as _Extension
from setuptools.command.build_ext import build_ext as _build_ext
except ImportError:
from distutils.command.build_ext import build_ext as _build_ext
from distutils.extension import Extension as _Extension
from distutils.command.build_ext import ( # type: ignore[assignment]
build_ext as _build_ext,
)
from distutils.extension import Extension as _Extension # type: ignore[assignment]
import distutils.errors
import distutils.ccompiler
import distutils.errors
WIN = sys.platform.startswith("win32")
PY2 = sys.version_info[0] < 3
WIN = sys.platform.startswith("win32") and "mingw" not in sysconfig.get_platform()
MACOS = sys.platform.startswith("darwin")
STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}"
@@ -83,7 +98,7 @@ class Pybind11Extension(_Extension):
* ``stdlib=libc++`` on macOS
* ``visibility=hidden`` and ``-g0`` on Unix
Finally, you can set ``cxx_std`` via constructor or afterwords to enable
Finally, you can set ``cxx_std`` via constructor or afterwards to enable
flags for C++ std, and a few extra helper flags related to the C++ standard
level. It is _highly_ recommended you either set this, or use the provided
``build_ext``, which will search for the highest supported extension for
@@ -93,23 +108,18 @@ class Pybind11Extension(_Extension):
If you want to add pybind11 headers manually, for example for an exact
git checkout, then set ``include_pybind11=False``.
Warning: do not use property-based access to the instance on Python 2 -
this is an ugly old-style class due to Distutils.
"""
def _add_cflags(self, *flags):
for flag in flags:
if flag not in self.extra_compile_args:
self.extra_compile_args.append(flag)
# flags are prepended, so that they can be further overridden, e.g. by
# ``extra_compile_args=["-g"]``.
def _add_lflags(self, *flags):
for flag in flags:
if flag not in self.extra_link_args:
self.extra_link_args.append(flag)
def _add_cflags(self, flags: list[str]) -> None:
self.extra_compile_args[:0] = flags
def __init__(self, *args, **kwargs):
def _add_ldflags(self, flags: list[str]) -> None:
self.extra_link_args[:0] = flags
def __init__(self, *args: Any, **kwargs: Any) -> None:
self._cxx_level = 0
cxx_std = kwargs.pop("cxx_std", 0)
@@ -118,9 +128,7 @@ class Pybind11Extension(_Extension):
include_pybind11 = kwargs.pop("include_pybind11", True)
# Can't use super here because distutils has old-style classes in
# Python 2!
_Extension.__init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
# Include the installed package pybind11 headers
if include_pybind11:
@@ -132,36 +140,40 @@ class Pybind11Extension(_Extension):
if pyinc not in self.include_dirs:
self.include_dirs.append(pyinc)
except ImportError:
except ModuleNotFoundError:
pass
# Have to use the accessor manually to support Python 2 distutils
Pybind11Extension.cxx_std.__set__(self, cxx_std)
self.cxx_std = cxx_std
cflags = []
if WIN:
self._add_cflags("/EHsc", "/bigobj")
cflags += ["/EHsc", "/bigobj"]
else:
self._add_cflags("-fvisibility=hidden", "-g0")
if MACOS:
self._add_cflags("-stdlib=libc++")
self._add_lflags("-stdlib=libc++")
cflags += ["-fvisibility=hidden"]
env_cflags = os.environ.get("CFLAGS", "")
env_cppflags = os.environ.get("CPPFLAGS", "")
c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags)
if not any(opt.startswith("-g") for opt in c_cpp_flags):
cflags += ["-g0"]
self._add_cflags(cflags)
@property
def cxx_std(self):
def cxx_std(self) -> int:
"""
The CXX standard level. If set, will add the required flags. If left
at 0, it will trigger an automatic search when pybind11's build_ext
is used. If None, will have no effect. Besides just the flags, this
may add a register warning/error fix for Python 2 or macos-min 10.9
or 10.14.
The CXX standard level. If set, will add the required flags. If left at
0, it will trigger an automatic search when pybind11's build_ext is
used. If None, will have no effect. Besides just the flags, this may
add a macos-min 10.9 or 10.14 flag if MACOSX_DEPLOYMENT_TARGET is
unset.
"""
return self._cxx_level
@cxx_std.setter
def cxx_std(self, level):
def cxx_std(self, level: int) -> None:
if self._cxx_level:
warnings.warn("You cannot safely change the cxx_level after setting it!")
warnings.warn(
"You cannot safely change the cxx_level after setting it!", stacklevel=2
)
# MSVC 2015 Update 3 and later only have 14 (and later 17) modes, so
# force a valid flag here.
@@ -173,35 +185,32 @@ class Pybind11Extension(_Extension):
if not level:
return
self.extra_compile_args.append(STD_TMPL.format(level))
cflags = [STD_TMPL.format(level)]
ldflags = []
if MACOS and "MACOSX_DEPLOYMENT_TARGET" not in os.environ:
# C++17 requires a higher min version of macOS. An earlier version
# can be set manually via environment variable if you are careful
# in your feature usage, but 10.14 is the safest setting for
# general use.
macosx_min = "-mmacosx-version-min=" + ("10.9" if level < 17 else "10.14")
self.extra_compile_args.append(macosx_min)
self.extra_link_args.append(macosx_min)
# (10.12 or 10.13) can be set manually via environment variable if
# you are careful in your feature usage, but 10.14 is the safest
# setting for general use. However, never set higher than the
# current macOS version!
current_macos = tuple(int(x) for x in platform.mac_ver()[0].split(".")[:2])
desired_macos = (10, 9) if level < 17 else (10, 14)
macos_string = ".".join(str(x) for x in min(current_macos, desired_macos))
macosx_min = f"-mmacosx-version-min={macos_string}"
cflags += [macosx_min]
ldflags += [macosx_min]
if PY2:
if WIN:
# Will be ignored on MSVC 2015, where C++17 is not supported so
# this flag is not valid.
self.extra_compile_args.append("/wd5033")
elif level >= 17:
self.extra_compile_args.append("-Wno-register")
elif level >= 14:
self.extra_compile_args.append("-Wno-deprecated-register")
self._add_cflags(cflags)
self._add_ldflags(ldflags)
# Just in case someone clever tries to multithread
tmp_chdir_lock = threading.Lock()
cpp_cache_lock = threading.Lock()
@contextlib.contextmanager
def tmp_chdir():
def tmp_chdir() -> Iterator[str]:
"Prepare and enter a temporary directory, cleanup when done"
# Threadsafe
@@ -217,7 +226,7 @@ def tmp_chdir():
# cf http://bugs.python.org/issue26689
def has_flag(compiler, flag):
def has_flag(compiler: Any, flag: str) -> bool:
"""
Return the flag if a flag name is supported on the
specified compiler, otherwise None (can be used as a boolean).
@@ -225,12 +234,12 @@ def has_flag(compiler, flag):
"""
with tmp_chdir():
fname = "flagcheck.cpp"
with open(fname, "w") as f:
f.write("int main (int argc, char **argv) { return 0; }")
fname = Path("flagcheck.cpp")
# Don't trigger -Wunused-parameter.
fname.write_text("int main (int, char **) { return 0; }", encoding="utf-8")
try:
compiler.compile([fname], extra_postargs=[flag])
compiler.compile([str(fname)], extra_postargs=[flag])
except distutils.errors.CompileError:
return False
return True
@@ -240,7 +249,8 @@ def has_flag(compiler, flag):
cpp_flag_cache = None
def auto_cpp_level(compiler):
@lru_cache()
def auto_cpp_level(compiler: Any) -> str | int:
"""
Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows.
"""
@@ -248,19 +258,10 @@ def auto_cpp_level(compiler):
if WIN:
return "latest"
global cpp_flag_cache
# If this has been previously calculated with the same args, return that
with cpp_cache_lock:
if cpp_flag_cache:
return cpp_flag_cache
levels = [17, 14, 11]
for level in levels:
if has_flag(compiler, STD_TMPL.format(level)):
with cpp_cache_lock:
cpp_flag_cache = level
return level
msg = "Unsupported compiler -- at least C++11 support is needed!"
@@ -270,22 +271,97 @@ def auto_cpp_level(compiler):
class build_ext(_build_ext): # noqa: N801
"""
Customized build_ext that allows an auto-search for the highest supported
C++ level for Pybind11Extension.
C++ level for Pybind11Extension. This is only needed for the auto-search
for now, and is completely optional otherwise.
"""
def build_extensions(self):
def build_extensions(self) -> None:
"""
Build extensions, injecting C++ std for Pybind11Extension if needed.
"""
for ext in self.extensions:
if hasattr(ext, "_cxx_level") and ext._cxx_level == 0:
# Python 2 syntax - old-style distutils class
ext.__class__.cxx_std.__set__(ext, auto_cpp_level(self.compiler))
ext.cxx_std = auto_cpp_level(self.compiler)
# Python 2 doesn't allow super here, since distutils uses old-style
# classes!
_build_ext.build_extensions(self)
super().build_extensions()
def intree_extensions(
paths: Iterable[str], package_dir: dict[str, str] | None = None
) -> list[Pybind11Extension]:
"""
Generate Pybind11Extensions from source files directly located in a Python
source tree.
``package_dir`` behaves as in ``setuptools.setup``. If unset, the Python
package root parent is determined as the first parent directory that does
not contain an ``__init__.py`` file.
"""
exts = []
if package_dir is None:
for path in paths:
parent, _ = os.path.split(path)
while os.path.exists(os.path.join(parent, "__init__.py")):
parent, _ = os.path.split(parent)
relname, _ = os.path.splitext(os.path.relpath(path, parent))
qualified_name = relname.replace(os.path.sep, ".")
exts.append(Pybind11Extension(qualified_name, [path]))
return exts
for path in paths:
for prefix, parent in package_dir.items():
if path.startswith(parent):
relname, _ = os.path.splitext(os.path.relpath(path, parent))
qualified_name = relname.replace(os.path.sep, ".")
if prefix:
qualified_name = prefix + "." + qualified_name
exts.append(Pybind11Extension(qualified_name, [path]))
break
else:
msg = (
f"path {path} is not a child of any of the directories listed "
f"in 'package_dir' ({package_dir})"
)
raise ValueError(msg)
return exts
def naive_recompile(obj: str, src: str) -> bool:
"""
This will recompile only if the source file changes. It does not check
header files, so a more advanced function or Ccache is better if you have
editable header files in your package.
"""
return os.stat(obj).st_mtime < os.stat(src).st_mtime
def no_recompile(obg: str, src: str) -> bool: # noqa: ARG001
"""
This is the safest but slowest choice (and is the default) - will always
recompile sources.
"""
return True
S = TypeVar("S", bound="ParallelCompile")
CCompilerMethod = Callable[
[
distutils.ccompiler.CCompiler,
List[str],
Optional[str],
Optional[List[Union[Tuple[str], Tuple[str, Optional[str]]]]],
Optional[List[str]],
bool,
Optional[List[str]],
Optional[List[str]],
Optional[List[str]],
],
List[str],
]
# Optional parallel compile utility
@@ -293,55 +369,78 @@ class build_ext(_build_ext): # noqa: N801
# and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py
# and NumPy's parallel distutils module:
# https://github.com/numpy/numpy/blob/master/numpy/distutils/ccompiler.py
class ParallelCompile(object):
class ParallelCompile:
"""
Make a parallel compile function. Inspired by
numpy.distutils.ccompiler.CCompiler_compile and cppimport.
numpy.distutils.ccompiler.CCompiler.compile and cppimport.
This takes several arguments that allow you to customize the compile
function created:
envvar: Set an environment variable to control the compilation threads, like NPY_NUM_BUILD_JOBS
default: 0 will automatically multithread, or 1 will only multithread if the envvar is set.
max: The limit for automatic multithreading if non-zero
envvar:
Set an environment variable to control the compilation threads, like
NPY_NUM_BUILD_JOBS
default:
0 will automatically multithread, or 1 will only multithread if the
envvar is set.
max:
The limit for automatic multithreading if non-zero
needs_recompile:
A function of (obj, src) that returns True when recompile is needed. No
effect in isolated mode; use ccache instead, see
https://github.com/matplotlib/matplotlib/issues/1507/
To use::
To use:
ParallelCompile("NPY_NUM_BUILD_JOBS").install()
or:
or::
with ParallelCompile("NPY_NUM_BUILD_JOBS"):
setup(...)
By default, this assumes all files need to be recompiled. A smarter
function can be provided via needs_recompile. If the output has not yet
been generated, the compile will always run, and this function is not
called.
"""
__slots__ = ("envvar", "default", "max", "old")
__slots__ = ("envvar", "default", "max", "_old", "needs_recompile")
def __init__(self, envvar=None, default=0, max=0):
def __init__(
self,
envvar: str | None = None,
default: int = 0,
max: int = 0, # pylint: disable=redefined-builtin
needs_recompile: Callable[[str, str], bool] = no_recompile,
) -> None:
self.envvar = envvar
self.default = default
self.max = max
self.old = []
self.needs_recompile = needs_recompile
self._old: list[CCompilerMethod] = []
def function(self):
def function(self) -> CCompilerMethod:
"""
Builds a function object usable as distutils.ccompiler.CCompiler.compile.
"""
def compile_function(
compiler,
sources,
output_dir=None,
macros=None,
include_dirs=None,
debug=0,
extra_preargs=None,
extra_postargs=None,
depends=None,
):
compiler: distutils.ccompiler.CCompiler,
sources: list[str],
output_dir: str | None = None,
macros: list[tuple[str] | tuple[str, str | None]] | None = None,
include_dirs: list[str] | None = None,
debug: bool = False,
extra_preargs: list[str] | None = None,
extra_postargs: list[str] | None = None,
depends: list[str] | None = None,
) -> Any:
# These lines are directly from distutils.ccompiler.CCompiler
macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile(
macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined]
output_dir, macros, include_dirs, sources, depends, extra_postargs
)
cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs)
cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs) # type: ignore[attr-defined]
# The number of threads; start with default.
threads = self.default
@@ -350,15 +449,19 @@ class ParallelCompile(object):
if self.envvar is not None:
threads = int(os.environ.get(self.envvar, self.default))
def _single_compile(obj):
def _single_compile(obj: Any) -> None:
try:
src, ext = build[obj]
except KeyError:
return
compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
if not os.path.exists(obj) or self.needs_recompile(obj, src):
compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) # type: ignore[attr-defined]
try:
import multiprocessing
# Importing .synchronize checks for platforms that have some multiprocessing
# capabilities but lack semaphores, such as AWS Lambda and Android Termux.
import multiprocessing.synchronize
from multiprocessing.pool import ThreadPool
except ImportError:
threads = 1
@@ -371,8 +474,9 @@ class ParallelCompile(object):
threads = 1
if threads > 1:
for _ in ThreadPool(threads).imap_unordered(_single_compile, objects):
pass
with ThreadPool(threads) as pool:
for _ in pool.imap_unordered(_single_compile, objects):
pass
else:
for ob in objects:
_single_compile(ob)
@@ -381,13 +485,16 @@ class ParallelCompile(object):
return compile_function
def install(self):
distutils.ccompiler.CCompiler.compile = self.function()
def install(self: S) -> S:
"""
Installs the compile function into distutils.ccompiler.CCompiler.compile.
"""
distutils.ccompiler.CCompiler.compile = self.function() # type: ignore[assignment]
return self
def __enter__(self):
self.old.append(distutils.ccompiler.CCompiler.compile)
def __enter__(self: S) -> S:
self._old.append(distutils.ccompiler.CCompiler.compile)
return self.install()
def __exit__(self, *args):
distutils.ccompiler.CCompiler.compile = self.old.pop()
def __exit__(self, *args: Any) -> None:
distutils.ccompiler.CCompiler.compile = self._old.pop() # type: ignore[assignment]

View File

@@ -1,50 +0,0 @@
# IMPORTANT: Should stay in sync with setup_helpers.py (mostly checked by CI /
# pre-commit).
from typing import Any, Iterator, Optional, Type, TypeVar, Union
from types import TracebackType
from distutils.command.build_ext import build_ext as _build_ext # type: ignore
from distutils.extension import Extension as _Extension
import distutils.ccompiler
import contextlib
WIN: bool
PY2: bool
MACOS: bool
STD_TMPL: str
class Pybind11Extension(_Extension):
def _add_cflags(self, *flags: str) -> None: ...
def _add_lflags(self, *flags: str) -> None: ...
def __init__(
self, *args: Any, cxx_std: int = 0, language: str = "c++", **kwargs: Any
) -> None: ...
@property
def cxx_std(self) -> int: ...
@cxx_std.setter
def cxx_std(self, level: int) -> None: ...
@contextlib.contextmanager
def tmp_chdir() -> Iterator[str]: ...
def has_flag(compiler: distutils.ccompiler.CCompiler, flag: str) -> bool: ...
def auto_cpp_level(compiler: distutils.ccompiler.CCompiler) -> Union[int, str]: ...
class build_ext(_build_ext): # type: ignore
def build_extensions(self) -> None: ...
T = TypeVar("T", bound="ParallelCompile")
class ParallelCompile:
def __init__(
self, envvar: Optional[str] = None, default: int = 0, max: int = 0
): ...
def function(self) -> Any: ...
def install(self: T) -> T: ...
def __enter__(self: T) -> T: ...
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None: ...