aboutsummaryrefslogtreecommitdiff
path: root/pybind11
diff options
context:
space:
mode:
Diffstat (limited to 'pybind11')
-rw-r--r--pybind11/__init__.py11
-rw-r--r--pybind11/__main__.py26
-rw-r--r--pybind11/_version.py6
-rw-r--r--pybind11/_version.pyi6
-rw-r--r--pybind11/commands.py33
-rw-r--r--pybind11/setup_helpers.py262
-rw-r--r--pybind11/setup_helpers.pyi61
7 files changed, 211 insertions, 194 deletions
diff --git a/pybind11/__init__.py b/pybind11/__init__.py
index ad654208..7c10b305 100644
--- a/pybind11/__init__.py
+++ b/pybind11/__init__.py
@@ -1,12 +1,17 @@
-# -*- coding: utf-8 -*-
+import sys
-from ._version import version_info, __version__
-from .commands import get_include, get_cmake_dir
+if sys.version_info < (3, 6): # noqa: UP036
+ msg = "pybind11 does not support Python < 3.6. 2.9 was the last release supporting Python 2.7 and 3.5."
+ 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",
)
diff --git a/pybind11/__main__.py b/pybind11/__main__.py
index 020988c6..180665c2 100644
--- a/pybind11/__main__.py
+++ b/pybind11/__main__.py
@@ -1,15 +1,14 @@
-# -*- coding: utf-8 -*-
-from __future__ import print_function
+# pylint: disable=missing-function-docstring
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,11 +24,15 @@ 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",
help="Include flags for both pybind11 and Python headers.",
@@ -39,6 +42,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 +54,8 @@ def main():
print_includes()
if args.cmakedir:
print(get_cmake_dir())
+ if args.pkgconfigdir:
+ print(get_pkgconfig_dir())
if __name__ == "__main__":
diff --git a/pybind11/_version.py b/pybind11/_version.py
index f8b795ee..1c310e0c 100644
--- a/pybind11/_version.py
+++ b/pybind11/_version.py
@@ -1,12 +1,12 @@
-# -*- coding: utf-8 -*-
+from typing import Union
-def _to_int(s):
+def _to_int(s: str) -> Union[int, str]:
try:
return int(s)
except ValueError:
return s
-__version__ = "2.6.2"
+__version__ = "2.11.0"
version_info = tuple(_to_int(s) for s in __version__.split("."))
diff --git a/pybind11/_version.pyi b/pybind11/_version.pyi
deleted file mode 100644
index 970184c7..00000000
--- a/pybind11/_version.pyi
+++ /dev/null
@@ -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], ...]
diff --git a/pybind11/commands.py b/pybind11/commands.py
index 34dbaf8a..b11690f4 100644
--- a/pybind11/commands.py
+++ b/pybind11/commands.py
@@ -1,22 +1,37 @@
-# -*- coding: utf-8 -*-
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)
diff --git a/pybind11/setup_helpers.py b/pybind11/setup_helpers.py
index c69064ca..aeeee9dc 100644
--- a/pybind11/setup_helpers.py
+++ b/pybind11/setup_helpers.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
"""
This module provides helpers for C++11+ projects using pybind11.
@@ -41,26 +39,40 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import contextlib
import os
+import platform
+import shlex
import shutil
import sys
+import sysconfig
import tempfile
import threading
-import platform
import warnings
+from functools import lru_cache
+from pathlib import Path
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ 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 build_ext as _build_ext # type: ignore[assignment]
+ 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++{}"
@@ -84,7 +96,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
@@ -94,22 +106,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.
"""
# flags are prepended, so that they can be further overridden, e.g. by
# ``extra_compile_args=["-g"]``.
- def _add_cflags(self, flags):
+ def _add_cflags(self, flags: List[str]) -> None:
self.extra_compile_args[:0] = flags
- def _add_ldflags(self, flags):
+ def _add_ldflags(self, flags: List[str]) -> None:
self.extra_link_args[:0] = flags
- def __init__(self, *args, **kwargs):
-
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
self._cxx_level = 0
cxx_std = kwargs.pop("cxx_std", 0)
@@ -118,9 +126,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,40 +138,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 = []
- ldflags = []
if WIN:
cflags += ["/EHsc", "/bigobj"]
else:
- cflags += ["-fvisibility=hidden", "-g0"]
- if MACOS:
- cflags += ["-stdlib=libc++"]
- ldflags += ["-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)
- self._add_ldflags(ldflags)
@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.
@@ -189,31 +195,20 @@ class Pybind11Extension(_Extension):
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 = "-mmacosx-version-min=" + macos_string
+ 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.
- cflags += ["/wd5033"]
- elif level >= 17:
- cflags += ["-Wno-register"]
- elif level >= 14:
- cflags += ["-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
@@ -229,7 +224,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).
@@ -237,13 +232,12 @@ def has_flag(compiler, flag):
"""
with tmp_chdir():
- fname = "flagcheck.cpp"
- with open(fname, "w") as f:
- # Don't trigger -Wunused-parameter.
- f.write("int main (int, char **) { 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
@@ -253,7 +247,8 @@ def has_flag(compiler, flag):
cpp_flag_cache = None
-def auto_cpp_level(compiler):
+@lru_cache()
+def auto_cpp_level(compiler: Any) -> Union[str, int]:
"""
Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows.
"""
@@ -261,19 +256,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!"
@@ -287,22 +273,61 @@ class build_ext(_build_ext): # noqa: N801
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)
+
+ super().build_extensions()
+
+
+def intree_extensions(
+ paths: Iterable[str], package_dir: Optional[Dict[str, str]] = 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)
- # Python 2 doesn't allow super here, since distutils uses old-style
- # classes!
- _build_ext.build_extensions(self)
+ return exts
-def naive_recompile(obj, src):
+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
@@ -311,7 +336,7 @@ def naive_recompile(obj, src):
return os.stat(obj).st_mtime < os.stat(src).st_mtime
-def no_recompile(obg, src):
+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.
@@ -319,15 +344,33 @@ def no_recompile(obg, src):
return True
+S = TypeVar("S", bound="ParallelCompile")
+
+CCompilerMethod = Callable[
+ [
+ distutils.ccompiler.CCompiler,
+ List[str],
+ Optional[str],
+ Optional[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
# inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
# 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:
@@ -362,35 +405,40 @@ class ParallelCompile(object):
__slots__ = ("envvar", "default", "max", "_old", "needs_recompile")
- def __init__(self, envvar=None, default=0, max=0, needs_recompile=no_recompile):
+ def __init__(
+ self,
+ envvar: Optional[str] = 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.needs_recompile = needs_recompile
- self._old = []
+ 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: Optional[str] = None,
+ macros: Optional[Union[Tuple[str], Tuple[str, Optional[str]]]] = None,
+ include_dirs: Optional[List[str]] = None,
+ debug: bool = False,
+ extra_preargs: Optional[List[str]] = None,
+ extra_postargs: Optional[List[str]] = None,
+ depends: Optional[List[str]] = 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
@@ -399,17 +447,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
if not os.path.exists(obj) or self.needs_recompile(obj, src):
- compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
+ 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
@@ -422,8 +472,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)
@@ -432,13 +483,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):
+ 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]
diff --git a/pybind11/setup_helpers.pyi b/pybind11/setup_helpers.pyi
deleted file mode 100644
index 23232e1f..00000000
--- a/pybind11/setup_helpers.pyi
+++ /dev/null
@@ -1,61 +0,0 @@
-# IMPORTANT: Should stay in sync with setup_helpers.py (mostly checked by CI /
-# pre-commit).
-
-from typing import Any, Callable, 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: ...
-
-def no_recompile(obj: str, src: str) -> bool: ...
-def naive_recompile(obj: str, src: str) -> bool: ...
-
-T = TypeVar("T", bound="ParallelCompile")
-
-class ParallelCompile:
- envvar: Optional[str]
- default: int
- max: int
- needs_recompile: Callable[[str, str], bool]
- def __init__(
- self,
- envvar: Optional[str] = None,
- default: int = 0,
- max: int = 0,
- needs_recompile: Callable[[str, str], bool] = no_recompile,
- ) -> None: ...
- 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: ...