mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +00:00
feat: way to only recompile changed files (#2643)
* feat: lazy compile * refactor: lazy -> only_changed * refactor: leave the changed function up to the user * refactor: pass a function, based on @YannickJadoul and @HDembinski's suggestions * refactor: old -> _old, as it's not intended for users * docs: slight improvmenent from @rwgk * docs: Ccache spelling, extra warning about pip caching Ccache spelling noted by @YannickJadoul
This commit is contained in:
parent
c58758d049
commit
ebd5c5b48c
@ -87,7 +87,7 @@ repos:
|
|||||||
- id: disallow-caps
|
- id: disallow-caps
|
||||||
name: Disallow improper capitalization
|
name: Disallow improper capitalization
|
||||||
language: pygrep
|
language: pygrep
|
||||||
entry: PyBind|Numpy|Cmake
|
entry: PyBind|Numpy|Cmake|CCache
|
||||||
exclude: .pre-commit-config.yaml
|
exclude: .pre-commit-config.yaml
|
||||||
|
|
||||||
- repo: local
|
- repo: local
|
||||||
|
@ -89,6 +89,36 @@ default number of threads (0 will take the number of threads available) and
|
|||||||
``max=N``, the maximum number of threads; if you have a large extension you may
|
``max=N``, the maximum number of threads; if you have a large extension you may
|
||||||
want set this to a memory dependent number.
|
want set this to a memory dependent number.
|
||||||
|
|
||||||
|
If you are developing rapidly and have a lot of C++ files, you may want to
|
||||||
|
avoid rebuilding files that have not changed. For simple cases were you are
|
||||||
|
using ``pip install -e .`` and do not have local headers, you can skip the
|
||||||
|
rebuild if a object file is newer than it's source (headers are not checked!)
|
||||||
|
with the following:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pybind11.setup_helpers import ParallelCompile, naive_recompile
|
||||||
|
|
||||||
|
SmartCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile).install()
|
||||||
|
|
||||||
|
|
||||||
|
If you have a more complex build, you can implement a smarter function and pass
|
||||||
|
it to ``needs_recompile``, or you can use [Ccache]_ instead. ``CXX="cache g++"
|
||||||
|
pip install -e .`` would be the way to use it with GCC, for example. Unlike the
|
||||||
|
simple solution, this even works even when not compiling in editable mode, but
|
||||||
|
it does require Ccache to be installed.
|
||||||
|
|
||||||
|
Keep in mind that Pip will not even attempt to rebuild if it thinks it has
|
||||||
|
already built a copy of your code, which it deduces from the version number.
|
||||||
|
One way to avoid this is to use [setuptools_scm]_, which will generate a
|
||||||
|
version number that includes the number of commits since your last tag and a
|
||||||
|
hash for a dirty directory. Another way to force a rebuild is purge your cache
|
||||||
|
or use Pip's ``--no-cache-dir`` option.
|
||||||
|
|
||||||
|
.. [Ccache] https://ccache.dev
|
||||||
|
|
||||||
|
.. [setuptools_scm] https://github.com/pypa/setuptools_scm
|
||||||
|
|
||||||
.. _setup_helpers-pep518:
|
.. _setup_helpers-pep518:
|
||||||
|
|
||||||
PEP 518 requirements (Pip 10+ required)
|
PEP 518 requirements (Pip 10+ required)
|
||||||
|
@ -275,7 +275,8 @@ def auto_cpp_level(compiler):
|
|||||||
class build_ext(_build_ext): # noqa: N801
|
class build_ext(_build_ext): # noqa: N801
|
||||||
"""
|
"""
|
||||||
Customized build_ext that allows an auto-search for the highest supported
|
Customized build_ext that allows an auto-search for the highest supported
|
||||||
C++ level for Pybind11Extension.
|
C++ level for Pybind11Extension. This is only needed for the auto-search
|
||||||
|
for now, and is completely optional otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def build_extensions(self):
|
def build_extensions(self):
|
||||||
@ -293,6 +294,23 @@ class build_ext(_build_ext): # noqa: N801
|
|||||||
_build_ext.build_extensions(self)
|
_build_ext.build_extensions(self)
|
||||||
|
|
||||||
|
|
||||||
|
def naive_recompile(obj, src):
|
||||||
|
"""
|
||||||
|
This will recompile only if the source file changes. It does not check
|
||||||
|
header files, so a more advanced function or Ccache is better if you have
|
||||||
|
editable header files in your package.
|
||||||
|
"""
|
||||||
|
return os.stat(obj).st_mtime < os.stat(src).st_mtime
|
||||||
|
|
||||||
|
|
||||||
|
def no_recompile(obg, src):
|
||||||
|
"""
|
||||||
|
This is the safest but slowest choice (and is the default) - will always
|
||||||
|
recompile sources.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# Optional parallel compile utility
|
# Optional parallel compile utility
|
||||||
# inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
|
# inspired by: http://stackoverflow.com/questions/11013851/speeding-up-build-process-with-distutils
|
||||||
# and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py
|
# and: https://github.com/tbenthompson/cppimport/blob/stable/cppimport/build_module.py
|
||||||
@ -306,24 +324,42 @@ class ParallelCompile(object):
|
|||||||
This takes several arguments that allow you to customize the compile
|
This takes several arguments that allow you to customize the compile
|
||||||
function created:
|
function created:
|
||||||
|
|
||||||
envvar: Set an environment variable to control the compilation threads, like NPY_NUM_BUILD_JOBS
|
envvar:
|
||||||
default: 0 will automatically multithread, or 1 will only multithread if the envvar is set.
|
Set an environment variable to control the compilation threads, like
|
||||||
max: The limit for automatic multithreading if non-zero
|
NPY_NUM_BUILD_JOBS
|
||||||
|
default:
|
||||||
|
0 will automatically multithread, or 1 will only multithread if the
|
||||||
|
envvar is set.
|
||||||
|
max:
|
||||||
|
The limit for automatic multithreading if non-zero
|
||||||
|
needs_recompile:
|
||||||
|
A function of (obj, src) that returns True when recompile is needed. No
|
||||||
|
effect in isolated mode; use ccache instead, see
|
||||||
|
https://github.com/matplotlib/matplotlib/issues/1507/
|
||||||
|
|
||||||
|
To use::
|
||||||
|
|
||||||
To use:
|
|
||||||
ParallelCompile("NPY_NUM_BUILD_JOBS").install()
|
ParallelCompile("NPY_NUM_BUILD_JOBS").install()
|
||||||
or:
|
|
||||||
|
or::
|
||||||
|
|
||||||
with ParallelCompile("NPY_NUM_BUILD_JOBS"):
|
with ParallelCompile("NPY_NUM_BUILD_JOBS"):
|
||||||
setup(...)
|
setup(...)
|
||||||
|
|
||||||
|
By default, this assumes all files need to be recompiled. A smarter
|
||||||
|
function can be provided via needs_recompile. If the output has not yet
|
||||||
|
been generated, the compile will always run, and this function is not
|
||||||
|
called.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("envvar", "default", "max", "old")
|
__slots__ = ("envvar", "default", "max", "_old", "needs_recompile")
|
||||||
|
|
||||||
def __init__(self, envvar=None, default=0, max=0):
|
def __init__(self, envvar=None, default=0, max=0, needs_recompile=no_recompile):
|
||||||
self.envvar = envvar
|
self.envvar = envvar
|
||||||
self.default = default
|
self.default = default
|
||||||
self.max = max
|
self.max = max
|
||||||
self.old = []
|
self.needs_recompile = needs_recompile
|
||||||
|
self._old = []
|
||||||
|
|
||||||
def function(self):
|
def function(self):
|
||||||
"""
|
"""
|
||||||
@ -360,7 +396,9 @@ class ParallelCompile(object):
|
|||||||
src, ext = build[obj]
|
src, ext = build[obj]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
|
|
||||||
|
if not os.path.exists(obj) or self.needs_recompile(obj, src):
|
||||||
|
compiler._compile(obj, src, ext, cc_args, extra_postargs, pp_opts)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
@ -391,8 +429,8 @@ class ParallelCompile(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.old.append(distutils.ccompiler.CCompiler.compile)
|
self._old.append(distutils.ccompiler.CCompiler.compile)
|
||||||
return self.install()
|
return self.install()
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
distutils.ccompiler.CCompiler.compile = self.old.pop()
|
distutils.ccompiler.CCompiler.compile = self._old.pop()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# IMPORTANT: Should stay in sync with setup_helpers.py (mostly checked by CI /
|
# IMPORTANT: Should stay in sync with setup_helpers.py (mostly checked by CI /
|
||||||
# pre-commit).
|
# pre-commit).
|
||||||
|
|
||||||
from typing import Any, Iterator, Optional, Type, TypeVar, Union
|
from typing import Any, Callable, Iterator, Optional, Type, TypeVar, Union
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
|
|
||||||
from distutils.command.build_ext import build_ext as _build_ext # type: ignore
|
from distutils.command.build_ext import build_ext as _build_ext # type: ignore
|
||||||
@ -33,12 +33,23 @@ def auto_cpp_level(compiler: distutils.ccompiler.CCompiler) -> Union[int, str]:
|
|||||||
class build_ext(_build_ext): # type: ignore
|
class build_ext(_build_ext): # type: ignore
|
||||||
def build_extensions(self) -> None: ...
|
def build_extensions(self) -> None: ...
|
||||||
|
|
||||||
|
def no_recompile(obj: str, src: str) -> bool: ...
|
||||||
|
def naive_recompile(obj: str, src: str) -> bool: ...
|
||||||
|
|
||||||
T = TypeVar("T", bound="ParallelCompile")
|
T = TypeVar("T", bound="ParallelCompile")
|
||||||
|
|
||||||
class ParallelCompile:
|
class ParallelCompile:
|
||||||
|
envvar: Optional[str]
|
||||||
|
default: int
|
||||||
|
max: int
|
||||||
|
needs_recompile: Callable[[str, str], bool]
|
||||||
def __init__(
|
def __init__(
|
||||||
self, envvar: Optional[str] = None, default: int = 0, max: int = 0
|
self,
|
||||||
): ...
|
envvar: Optional[str] = None,
|
||||||
|
default: int = 0,
|
||||||
|
max: int = 0,
|
||||||
|
needs_recompile: Callable[[str, str], bool] = no_recompile,
|
||||||
|
) -> None: ...
|
||||||
def function(self) -> Any: ...
|
def function(self) -> Any: ...
|
||||||
def install(self: T) -> T: ...
|
def install(self: T) -> T: ...
|
||||||
def __enter__(self: T) -> T: ...
|
def __enter__(self: T) -> T: ...
|
||||||
|
Loading…
Reference in New Issue
Block a user