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 shutil 45import sys 46import tempfile 47import threading 48import platform 49import warnings 50 51try: 52 from setuptools.command.build_ext import build_ext as _build_ext 53 from setuptools import Extension as _Extension 54except ImportError: 55 from distutils.command.build_ext import build_ext as _build_ext 56 from distutils.extension import Extension as _Extension 57 58import distutils.errors 59import distutils.ccompiler 60 61 62WIN = sys.platform.startswith("win32") 63PY2 = sys.version_info[0] < 3 64MACOS = sys.platform.startswith("darwin") 65STD_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 75class 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 211tmp_chdir_lock = threading.Lock() 212cpp_cache_lock = threading.Lock() 213 214 215@contextlib.contextmanager 216def 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 232def 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 253cpp_flag_cache = None 254 255 256def 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 283class 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 305def 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 314def 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 327class ParallelCompile(object): 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() 445