|
49 | 49 | from distutils.extension import Extension as _Extension
|
50 | 50 |
|
51 | 51 | import distutils.errors
|
| 52 | +import distutils.ccompiler |
52 | 53 |
|
53 | 54 |
|
54 | 55 | WIN = sys.platform.startswith("win32")
|
@@ -279,3 +280,108 @@ def build_extensions(self):
|
279 | 280 | # Python 2 doesn't allow super here, since distutils uses old-style
|
280 | 281 | # classes!
|
281 | 282 | _build_ext.build_extensions(self)
|
| 283 | + |
| 284 | + |
| 285 | +# Optional parallel compile utility |
| 286 | +# inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils |
| 287 | +# and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py |
| 288 | +# and NumPy's parallel distutils module: |
| 289 | +# https://github.com/numpy/numpy/blob/master/numpy/distutils/ccompiler.py |
| 290 | +class ParallelCompile(object): |
| 291 | + """ |
| 292 | + Make a parallel compile function. Inspired by |
| 293 | + numpy.distutils.ccompiler.CCompiler_compile and cppimport. |
| 294 | +
|
| 295 | + This takes several arguments that allow you to customize the compile |
| 296 | + function created: |
| 297 | +
|
| 298 | + envvar: Set an environment variable to control the compilation threads, like NPY_NUM_BUILD_JOBS |
| 299 | + default: 0 will automatically multithread, or 1 will only multithread if the envvar is set. |
| 300 | + max: The limit for automatic multithreading if non-zero |
| 301 | +
|
| 302 | + To use: |
| 303 | + ParallelCompile("NPY_NUM_BUILD_JOBS").install() |
| 304 | + or: |
| 305 | + with ParallelCompile("NPY_NUM_BUILD_JOBS"): |
| 306 | + setup(...) |
| 307 | + """ |
| 308 | + |
| 309 | + __slots__ = ("envvar", "default", "max", "old") |
| 310 | + |
| 311 | + def __init__(self, envvar=None, default=0, max=0): |
| 312 | + self.envvar = envvar |
| 313 | + self.default = default |
| 314 | + self.max = max |
| 315 | + self.old = [] |
| 316 | + |
| 317 | + def function(self): |
| 318 | + """ |
| 319 | + Builds a function object usable as distutils.ccompiler.CCompiler.compile. |
| 320 | + """ |
| 321 | + |
| 322 | + def compile_function( |
| 323 | + compiler, |
| 324 | + sources, |
| 325 | + output_dir=None, |
| 326 | + macros=None, |
| 327 | + include_dirs=None, |
| 328 | + debug=0, |
| 329 | + extra_preargs=None, |
| 330 | + extra_postargs=None, |
| 331 | + depends=None, |
| 332 | + ): |
| 333 | + |
| 334 | + # These lines are directly from distutils.ccompiler.CCompiler |
| 335 | + macros, objects, extra_postargs, pp_opts, build = compiler._setup_compile( |
| 336 | + output_dir, macros, include_dirs, sources, depends, extra_postargs |
| 337 | + ) |
| 338 | + cc_args = compiler._get_cc_args(pp_opts, debug, extra_preargs) |
| 339 | + |
| 340 | + # The number of threads; start with default. |
| 341 | + threads = self.default |
| 342 | + |
| 343 | + # Determine the number of compilation threads, unless set by an environment variable. |
| 344 | + if self.envvar is not None: |
| 345 | + threads = int(os.environ.get(self.envvar, self.default)) |
| 346 | + |
| 347 | + def _single_compile(obj): |
| 348 | + try: |
| 349 | + src, ext = build[obj] |
| 350 | + except KeyError: |
| 351 | + return |
| 352 | + compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts) |
| 353 | + |
| 354 | + try: |
| 355 | + import multiprocessing |
| 356 | + from multiprocessing.pool import ThreadPool |
| 357 | + except ImportError: |
| 358 | + threads = 1 |
| 359 | + |
| 360 | + if threads == 0: |
| 361 | + try: |
| 362 | + threads = multiprocessing.cpu_count() |
| 363 | + threads = self.max if self.max and self.max < threads else threads |
| 364 | + except NotImplementedError: |
| 365 | + threads = 1 |
| 366 | + |
| 367 | + if threads > 1: |
| 368 | + for _ in ThreadPool(threads).imap_unordered(_single_compile, objects): |
| 369 | + pass |
| 370 | + else: |
| 371 | + for ob in objects: |
| 372 | + _single_compile(ob) |
| 373 | + |
| 374 | + return objects |
| 375 | + |
| 376 | + return compile_function |
| 377 | + |
| 378 | + def install(self): |
| 379 | + distutils.ccompiler.CCompiler.compile = self.function() |
| 380 | + return self |
| 381 | + |
| 382 | + def __enter__(self): |
| 383 | + self.old.append(distutils.ccompiler.CCompiler.compile) |
| 384 | + return self.install() |
| 385 | + |
| 386 | + def __exit__(self, *args): |
| 387 | + distutils.ccompiler.CCompiler.compile = self.old.pop() |
0 commit comments