diff options
Diffstat (limited to 'pybind11')
-rw-r--r-- | pybind11/__init__.py | 11 | ||||
-rw-r--r-- | pybind11/__main__.py | 26 | ||||
-rw-r--r-- | pybind11/_version.py | 6 | ||||
-rw-r--r-- | pybind11/_version.pyi | 6 | ||||
-rw-r--r-- | pybind11/commands.py | 33 | ||||
-rw-r--r-- | pybind11/setup_helpers.py | 262 | ||||
-rw-r--r-- | pybind11/setup_helpers.pyi | 61 |
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: ... |