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