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