setup_helpers.py
Go to the documentation of this file.
1 """
2 This module provides helpers for C++11+ projects using pybind11.
3 
4 LICENSE:
5 
6 Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, All rights reserved.
7 
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10 
11 1. Redistributions of source code must retain the above copyright notice, this
12  list of conditions and the following disclaimer.
13 
14 2. Redistributions in binary form must reproduce the above copyright notice,
15  this list of conditions and the following disclaimer in the documentation
16  and/or other materials provided with the distribution.
17 
18 3. Neither the name of the copyright holder nor the names of its contributors
19  may be used to endorse or promote products derived from this software
20  without specific prior written permission.
21 
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 """
33 
34 # IMPORTANT: If you change this file in the pybind11 repo, also review
35 # setup_helpers.pyi for matching changes.
36 #
37 # If you copy this file in, you don't
38 # need the .pyi file; it's just an interface file for static type checkers.
39 
40 import contextlib
41 import os
42 import platform
43 import shlex
44 import shutil
45 import sys
46 import sysconfig
47 import tempfile
48 import threading
49 import warnings
50 from functools import lru_cache
51 from pathlib import Path
52 from typing import (
53  Any,
54  Callable,
55  Dict,
56  Iterable,
57  Iterator,
58  List,
59  Optional,
60  Tuple,
61  TypeVar,
62  Union,
63 )
64 
65 try:
66  from setuptools import Extension as _Extension
67  from setuptools.command.build_ext import build_ext as _build_ext
68 except ImportError:
69  from distutils.command.build_ext import build_ext as _build_ext # type: ignore[assignment]
70  from distutils.extension import Extension as _Extension # type: ignore[assignment]
71 
72 import distutils.ccompiler
73 import distutils.errors
74 
75 WIN = sys.platform.startswith("win32") and "mingw" not in sysconfig.get_platform()
76 MACOS = sys.platform.startswith("darwin")
77 STD_TMPL = "/std:c++{}" if WIN else "-std=c++{}"
78 
79 
80 # It is recommended to use PEP 518 builds if using this module. However, this
81 # file explicitly supports being copied into a user's project directory
82 # standalone, and pulling pybind11 with the deprecated setup_requires feature.
83 # If you copy the file, remember to add it to your MANIFEST.in, and add the current
84 # directory into your path if it sits beside your setup.py.
85 
86 
87 class Pybind11Extension(_Extension):
88  """
89  Build a C++11+ Extension module with pybind11. This automatically adds the
90  recommended flags when you init the extension and assumes C++ sources - you
91  can further modify the options yourself.
92 
93  The customizations are:
94 
95  * ``/EHsc`` and ``/bigobj`` on Windows
96  * ``stdlib=libc++`` on macOS
97  * ``visibility=hidden`` and ``-g0`` on Unix
98 
99  Finally, you can set ``cxx_std`` via constructor or afterwards to enable
100  flags for C++ std, and a few extra helper flags related to the C++ standard
101  level. It is _highly_ recommended you either set this, or use the provided
102  ``build_ext``, which will search for the highest supported extension for
103  you if the ``cxx_std`` property is not set. Do not set the ``cxx_std``
104  property more than once, as flags are added when you set it. Set the
105  property to None to disable the addition of C++ standard flags.
106 
107  If you want to add pybind11 headers manually, for example for an exact
108  git checkout, then set ``include_pybind11=False``.
109  """
110 
111  # flags are prepended, so that they can be further overridden, e.g. by
112  # ``extra_compile_args=["-g"]``.
113 
114  def _add_cflags(self, flags: List[str]) -> None:
115  self.extra_compile_args[:0] = flags
116 
117  def _add_ldflags(self, flags: List[str]) -> None:
118  self.extra_link_args[:0] = flags
119 
120  def __init__(self, *args: Any, **kwargs: Any) -> None:
121  self._cxx_level = 0
122  cxx_std = kwargs.pop("cxx_std", 0)
123 
124  if "language" not in kwargs:
125  kwargs["language"] = "c++"
126 
127  include_pybind11 = kwargs.pop("include_pybind11", True)
128 
129  super().__init__(*args, **kwargs)
130 
131  # Include the installed package pybind11 headers
132  if include_pybind11:
133  # If using setup_requires, this fails the first time - that's okay
134  try:
135  import pybind11
136 
137  pyinc = pybind11.get_include()
138 
139  if pyinc not in self.include_dirs:
140  self.include_dirs.append(pyinc)
141  except ModuleNotFoundError:
142  pass
143 
144  self.cxx_std = cxx_std
145 
146  cflags = []
147  if WIN:
148  cflags += ["/EHsc", "/bigobj"]
149  else:
150  cflags += ["-fvisibility=hidden"]
151  env_cflags = os.environ.get("CFLAGS", "")
152  env_cppflags = os.environ.get("CPPFLAGS", "")
153  c_cpp_flags = shlex.split(env_cflags) + shlex.split(env_cppflags)
154  if not any(opt.startswith("-g") for opt in c_cpp_flags):
155  cflags += ["-g0"]
156  self._add_cflags(cflags)
157 
158  @property
159  def cxx_std(self) -> int:
160  """
161  The CXX standard level. If set, will add the required flags. If left at
162  0, it will trigger an automatic search when pybind11's build_ext is
163  used. If None, will have no effect. Besides just the flags, this may
164  add a macos-min 10.9 or 10.14 flag if MACOSX_DEPLOYMENT_TARGET is
165  unset.
166  """
167  return self._cxx_level
168 
169  @cxx_std.setter
170  def cxx_std(self, level: int) -> None:
171  if self._cxx_level:
172  warnings.warn(
173  "You cannot safely change the cxx_level after setting it!", stacklevel=2
174  )
175 
176  # MSVC 2015 Update 3 and later only have 14 (and later 17) modes, so
177  # force a valid flag here.
178  if WIN and level == 11:
179  level = 14
180 
181  self._cxx_level = level
182 
183  if not level:
184  return
185 
186  cflags = [STD_TMPL.format(level)]
187  ldflags = []
188 
189  if MACOS and "MACOSX_DEPLOYMENT_TARGET" not in os.environ:
190  # C++17 requires a higher min version of macOS. An earlier version
191  # (10.12 or 10.13) can be set manually via environment variable if
192  # you are careful in your feature usage, but 10.14 is the safest
193  # setting for general use. However, never set higher than the
194  # current macOS version!
195  current_macos = tuple(int(x) for x in platform.mac_ver()[0].split(".")[:2])
196  desired_macos = (10, 9) if level < 17 else (10, 14)
197  macos_string = ".".join(str(x) for x in min(current_macos, desired_macos))
198  macosx_min = f"-mmacosx-version-min={macos_string}"
199  cflags += [macosx_min]
200  ldflags += [macosx_min]
201 
202  self._add_cflags(cflags)
203  self._add_ldflags(ldflags)
204 
205 
206 # Just in case someone clever tries to multithread
207 tmp_chdir_lock = threading.Lock()
208 
209 
210 @contextlib.contextmanager
211 def tmp_chdir() -> Iterator[str]:
212  "Prepare and enter a temporary directory, cleanup when done"
213 
214  # Threadsafe
215  with tmp_chdir_lock:
216  olddir = os.getcwd()
217  try:
218  tmpdir = tempfile.mkdtemp()
219  os.chdir(tmpdir)
220  yield tmpdir
221  finally:
222  os.chdir(olddir)
223  shutil.rmtree(tmpdir)
224 
225 
226 # cf http://bugs.python.org/issue26689
227 def has_flag(compiler: Any, flag: str) -> bool:
228  """
229  Return the flag if a flag name is supported on the
230  specified compiler, otherwise None (can be used as a boolean).
231  If multiple flags are passed, return the first that matches.
232  """
233 
234  with tmp_chdir():
235  fname = Path("flagcheck.cpp")
236  # Don't trigger -Wunused-parameter.
237  fname.write_text("int main (int, char **) { return 0; }", encoding="utf-8")
238 
239  try:
240  compiler.compile([str(fname)], extra_postargs=[flag])
241  except distutils.errors.CompileError:
242  return False
243  return True
244 
245 
246 # Every call will cache the result
247 cpp_flag_cache = None
248 
249 
250 @lru_cache()
251 def auto_cpp_level(compiler: Any) -> Union[str, int]:
252  """
253  Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows.
254  """
255 
256  if WIN:
257  return "latest"
258 
259  levels = [17, 14, 11]
260 
261  for level in levels:
262  if has_flag(compiler, STD_TMPL.format(level)):
263  return level
264 
265  msg = "Unsupported compiler -- at least C++11 support is needed!"
266  raise RuntimeError(msg)
267 
268 
269 class build_ext(_build_ext): # noqa: N801
270  """
271  Customized build_ext that allows an auto-search for the highest supported
272  C++ level for Pybind11Extension. This is only needed for the auto-search
273  for now, and is completely optional otherwise.
274  """
275 
276  def build_extensions(self) -> None:
277  """
278  Build extensions, injecting C++ std for Pybind11Extension if needed.
279  """
280 
281  for ext in self.extensions:
282  if hasattr(ext, "_cxx_level") and ext._cxx_level == 0:
283  ext.cxx_std = auto_cpp_level(self.compiler)
284 
285  super().build_extensions()
286 
287 
289  paths: Iterable[str], package_dir: Optional[Dict[str, str]] = None
290 ) -> List[Pybind11Extension]:
291  """
292  Generate Pybind11Extensions from source files directly located in a Python
293  source tree.
294 
295  ``package_dir`` behaves as in ``setuptools.setup``. If unset, the Python
296  package root parent is determined as the first parent directory that does
297  not contain an ``__init__.py`` file.
298  """
299  exts = []
300 
301  if package_dir is None:
302  for path in paths:
303  parent, _ = os.path.split(path)
304  while os.path.exists(os.path.join(parent, "__init__.py")):
305  parent, _ = os.path.split(parent)
306  relname, _ = os.path.splitext(os.path.relpath(path, parent))
307  qualified_name = relname.replace(os.path.sep, ".")
308  exts.append(Pybind11Extension(qualified_name, [path]))
309  return exts
310 
311  for path in paths:
312  for prefix, parent in package_dir.items():
313  if path.startswith(parent):
314  relname, _ = os.path.splitext(os.path.relpath(path, parent))
315  qualified_name = relname.replace(os.path.sep, ".")
316  if prefix:
317  qualified_name = prefix + "." + qualified_name
318  exts.append(Pybind11Extension(qualified_name, [path]))
319  break
320  else:
321  msg = (
322  f"path {path} is not a child of any of the directories listed "
323  f"in 'package_dir' ({package_dir})"
324  )
325  raise ValueError(msg)
326 
327  return exts
328 
329 
330 def naive_recompile(obj: str, src: str) -> bool:
331  """
332  This will recompile only if the source file changes. It does not check
333  header files, so a more advanced function or Ccache is better if you have
334  editable header files in your package.
335  """
336  return os.stat(obj).st_mtime < os.stat(src).st_mtime
337 
338 
339 def no_recompile(obg: str, src: str) -> bool: # noqa: ARG001
340  """
341  This is the safest but slowest choice (and is the default) - will always
342  recompile sources.
343  """
344  return True
345 
346 
347 S = TypeVar("S", bound="ParallelCompile")
348 
349 CCompilerMethod = Callable[
350  [
351  distutils.ccompiler.CCompiler,
352  List[str],
353  Optional[str],
354  Optional[Union[Tuple[str], Tuple[str, Optional[str]]]],
355  Optional[List[str]],
356  bool,
357  Optional[List[str]],
358  Optional[List[str]],
359  Optional[List[str]],
360  ],
361  List[str],
362 ]
363 
364 
365 # Optional parallel compile utility
366 # inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
367 # and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py
368 # and NumPy's parallel distutils module:
369 # https://github.com/numpy/numpy/blob/master/numpy/distutils/ccompiler.py
371  """
372  Make a parallel compile function. Inspired by
373  numpy.distutils.ccompiler.CCompiler.compile and cppimport.
374 
375  This takes several arguments that allow you to customize the compile
376  function created:
377 
378  envvar:
379  Set an environment variable to control the compilation threads, like
380  NPY_NUM_BUILD_JOBS
381  default:
382  0 will automatically multithread, or 1 will only multithread if the
383  envvar is set.
384  max:
385  The limit for automatic multithreading if non-zero
386  needs_recompile:
387  A function of (obj, src) that returns True when recompile is needed. No
388  effect in isolated mode; use ccache instead, see
389  https://github.com/matplotlib/matplotlib/issues/1507/
390 
391  To use::
392 
393  ParallelCompile("NPY_NUM_BUILD_JOBS").install()
394 
395  or::
396 
397  with ParallelCompile("NPY_NUM_BUILD_JOBS"):
398  setup(...)
399 
400  By default, this assumes all files need to be recompiled. A smarter
401  function can be provided via needs_recompile. If the output has not yet
402  been generated, the compile will always run, and this function is not
403  called.
404  """
405 
406  __slots__ = ("envvar", "default", "max", "_old", "needs_recompile")
407 
408  def __init__(
409  self,
410  envvar: Optional[str] = None,
411  default: int = 0,
412  max: int = 0, # pylint: disable=redefined-builtin
413  needs_recompile: Callable[[str, str], bool] = no_recompile,
414  ) -> None:
415  self.envvar = envvar
416  self.default = default
417  self.max = max
418  self.needs_recompile = needs_recompile
419  self._old: List[CCompilerMethod] = []
420 
421  def function(self) -> CCompilerMethod:
422  """
423  Builds a function object usable as distutils.ccompiler.CCompiler.compile.
424  """
425 
426  def compile_function(
427  compiler: distutils.ccompiler.CCompiler,
428  sources: List[str],
429  output_dir: Optional[str] = None,
430  macros: Optional[Union[Tuple[str], Tuple[str, Optional[str]]]] = None,
431  include_dirs: Optional[List[str]] = None,
432  debug: bool = False,
433  extra_preargs: Optional[List[str]] = None,
434  extra_postargs: Optional[List[str]] = None,
435  depends: Optional[List[str]] = None,
436  ) -> Any:
437  # These lines are directly from distutils.ccompiler.CCompiler
438  macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( # type: ignore[attr-defined]
439  output_dir, macros, include_dirs, sources, depends, extra_postargs
440  )
441  cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs) # type: ignore[attr-defined]
442 
443  # The number of threads; start with default.
444  threads = self.default
445 
446  # Determine the number of compilation threads, unless set by an environment variable.
447  if self.envvar is not None:
448  threads = int(os.environ.get(self.envvar, self.default))
449 
450  def _single_compile(obj: Any) -> None:
451  try:
452  src, ext = build[obj]
453  except KeyError:
454  return
455 
456  if not os.path.exists(obj) or self.needs_recompile(obj, src):
457  compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) # type: ignore[attr-defined]
458 
459  try:
460  # Importing .synchronize checks for platforms that have some multiprocessing
461  # capabilities but lack semaphores, such as AWS Lambda and Android Termux.
462  import multiprocessing.synchronize
463  from multiprocessing.pool import ThreadPool
464  except ImportError:
465  threads = 1
466 
467  if threads == 0:
468  try:
469  threads = multiprocessing.cpu_count()
470  threads = self.max if self.max and self.max < threads else threads
471  except NotImplementedError:
472  threads = 1
473 
474  if threads > 1:
475  with ThreadPool(threads) as pool:
476  for _ in pool.imap_unordered(_single_compile, objects):
477  pass
478  else:
479  for ob in objects:
480  _single_compile(ob)
481 
482  return objects
483 
484  return compile_function
485 
486  def install(self: S) -> S:
487  """
488  Installs the compile function into distutils.ccompiler.CCompiler.compile.
489  """
490  distutils.ccompiler.CCompiler.compile = self.function() # type: ignore[assignment]
491  return self
492 
493  def __enter__(self: S) -> S:
494  self._old.append(distutils.ccompiler.CCompiler.compile)
495  return self.install()
496 
497  def __exit__(self, *args: Any) -> None:
498  distutils.ccompiler.CCompiler.compile = self._old.pop() # type: ignore[assignment]
gtsam.examples.DogLegOptimizerExample.int
int
Definition: DogLegOptimizerExample.py:111
pybind11.setup_helpers.intree_extensions
List[Pybind11Extension] intree_extensions(Iterable[str] paths, Optional[Dict[str, str]] package_dir=None)
Definition: setup_helpers.py:288
pybind11.setup_helpers.ParallelCompile
Definition: setup_helpers.py:370
pybind11.setup_helpers.ParallelCompile.__exit__
None __exit__(self, *Any args)
Definition: setup_helpers.py:497
pybind11.setup_helpers.ParallelCompile.max
max
Definition: setup_helpers.py:411
hasattr
bool hasattr(handle obj, handle name)
Definition: pytypes.h:853
pybind11.setup_helpers.tmp_chdir
Iterator[str] tmp_chdir()
Definition: setup_helpers.py:211
pybind11.setup_helpers.Pybind11Extension
Definition: setup_helpers.py:87
pybind11.setup_helpers.build_ext.build_extensions
None build_extensions(self)
Definition: setup_helpers.py:276
pybind11.setup_helpers.Pybind11Extension._add_cflags
None _add_cflags(self, List[str] flags)
Definition: setup_helpers.py:114
pybind11.setup_helpers.Pybind11Extension.cxx_std
cxx_std
Definition: setup_helpers.py:144
pybind11.setup_helpers.ParallelCompile.default
default
Definition: setup_helpers.py:410
pybind11.setup_helpers.naive_recompile
bool naive_recompile(str obj, str src)
Definition: setup_helpers.py:330
pybind11.setup_helpers.ParallelCompile.__enter__
S __enter__(S self)
Definition: setup_helpers.py:493
pybind11.setup_helpers.Pybind11Extension._add_ldflags
None _add_ldflags(self, List[str] flags)
Definition: setup_helpers.py:117
pybind11.setup_helpers.Pybind11Extension.__init__
None __init__(self, *Any args, **Any kwargs)
Definition: setup_helpers.py:120
str
Definition: pytypes.h:1524
pybind11.setup_helpers.ParallelCompile.needs_recompile
needs_recompile
Definition: setup_helpers.py:412
Eigen::ThreadPool
ThreadPoolTempl< StlThreadEnvironment > ThreadPool
Definition: NonBlockingThreadPool.h:482
pybind11.setup_helpers.ParallelCompile.function
CCompilerMethod function(self)
Definition: setup_helpers.py:421
pybind11.setup_helpers.auto_cpp_level
Union[str, int] auto_cpp_level(Any compiler)
Definition: setup_helpers.py:251
tuple
Definition: pytypes.h:2035
pybind11.setup_helpers.ParallelCompile.install
S install(S self)
Definition: setup_helpers.py:486
pybind11.setup_helpers.no_recompile
bool no_recompile(str obg, str src)
Definition: setup_helpers.py:339
min
#define min(a, b)
Definition: datatypes.h:19
gtsam::split
void split(const G &g, const PredecessorMap< KEY > &tree, G &Ab1, G &Ab2)
Definition: graph-inl.h:245
pybind11.setup_helpers.ParallelCompile.envvar
envvar
Definition: setup_helpers.py:409
pybind11.setup_helpers.has_flag
bool has_flag(Any compiler, str flag)
Definition: setup_helpers.py:227
pybind11.setup_helpers.ParallelCompile.__init__
None __init__(self, Optional[str] envvar=None, int default=0, int max=0, Callable[[str, str], bool] needs_recompile=no_recompile)
Definition: setup_helpers.py:408
pybind11.setup_helpers.Pybind11Extension._cxx_level
_cxx_level
Definition: setup_helpers.py:121
pybind11.setup_helpers.build_ext
Definition: setup_helpers.py:269


gtsam
Author(s):
autogenerated on Tue Jun 25 2024 03:02:42