Skip to content

Commit 2d8b8c8

Browse files
committed
chore: implement rudimentary CMake incremental build
We speed-up the build process for local development when CMake sources are not changed in between build runs. This reduces the build time from about 3 minutes to about 20 seconds.
1 parent 3ff7046 commit 2d8b8c8

File tree

1 file changed

+41
-5
lines changed

1 file changed

+41
-5
lines changed

setup.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pathlib import Path # isort: skip
2525
from pkg_resources import get_build_platform # isort: skip
2626
from distutils.command.clean import clean as CleanCommand # isort: skip
27+
from distutils.dep_util import newer_group
2728

2829

2930
try:
@@ -312,6 +313,8 @@ def run(self):
312313

313314

314315
class CMakeBuild(build_ext):
316+
INCREMENTAL = os.getenv("DD_CMAKE_INCREMENTAL_BUILD", "0").lower() in ("1", "yes", "on", "true")
317+
315318
@staticmethod
316319
def try_strip_symbols(so_file):
317320
if CURRENT_OS == "Linux" and shutil.which("strip") is not None:
@@ -350,6 +353,35 @@ def build_extension(self, ext):
350353
print(f"WARNING: An error occurred while building the extension: {e}")
351354

352355
def build_extension_cmake(self, ext):
356+
if IS_EDITABLE and self.INCREMENTAL:
357+
# DEV: Rudimentary incremental build support. We copy the logic from
358+
# setuptools' build_ext command, best effort.
359+
full_path = Path(self.get_ext_fullpath(ext.name))
360+
ext_path = Path(ext.source_dir, full_path.name)
361+
362+
# Collect all the source files within the source directory. We exclude
363+
# Python sources and anything that does not have a suffix (most likely
364+
# a binary file), or that has the same name as the extension binary.
365+
sources = (
366+
[
367+
_
368+
for _ in Path(ext.source_dir).rglob("**")
369+
if _.is_file() and _.name != full_path.name and _.suffix and _.suffix not in (".py", ".pyc", ".pyi")
370+
]
371+
if ext.source_dir
372+
else []
373+
)
374+
if not (self.force or newer_group([str(_.resolve()) for _ in sources], str(ext_path.resolve()), "newer")):
375+
print(f"skipping '{ext.name}' CMake extension (up-to-date)")
376+
377+
# We need to copy the binary where setuptools expects it
378+
full_path.parent.mkdir(parents=True, exist_ok=True)
379+
shutil.copy(ext_path, full_path)
380+
381+
return
382+
else:
383+
print(f"building '{ext.name}' CMake extension")
384+
353385
# Define the build and output directories
354386
output_dir = Path(self.get_ext_fullpath(ext.name)).parent.resolve()
355387
extension_basename = Path(self.get_ext_fullpath(ext.name)).name
@@ -459,11 +491,15 @@ def dump_metadata(cls):
459491
with open(cls.metadata_file, "w") as f:
460492
f.write(f"Total time: {total_s:0.2f}s\n")
461493
f.write("Environment:\n")
462-
f.write(f"\tCARGO_BUILD_JOBS: {os.getenv('CARGO_BUILD_JOBS', 'unset')}\n")
463-
f.write(f"\tCMAKE_BUILD_PARALLEL_LEVEL: {os.getenv('CMAKE_BUILD_PARALLEL_LEVEL', 'unset')}\n")
464-
f.write(f"\tDD_COMPILE_MODE: {COMPILE_MODE}\n")
465-
f.write(f"\tDD_USE_SCCACHE: {SCCACHE_COMPILE}\n")
466-
f.write(f"\tDD_FAST_BUILD: {FAST_BUILD}\n")
494+
for n, v in [
495+
("CARGO_BUILD_JOBS", os.getenv("CARGO_BUILD_JOBS", "unset")),
496+
("CMAKE_BUILD_PARALLEL_LEVEL", os.getenv("CMAKE_BUILD_PARALLEL_LEVEL", "unset")),
497+
("DD_COMPILE_MODE", COMPILE_MODE),
498+
("DD_USE_SCCACHE", SCCACHE_COMPILE),
499+
("DD_FAST_BUILD", FAST_BUILD),
500+
("DD_CMAKE_INCREMENTAL_BUILD", CMakeBuild.INCREMENTAL),
501+
]:
502+
print(f"\t{n}: {v}", file=f)
467503
f.write("Extension build times:\n")
468504
f.write(f"\tTotal: {build_total_s:0.2f}s ({build_percent:0.2f}%)\n")
469505
for ext, elapsed_ns in sorted(cls.build_times.items(), key=lambda x: x[1], reverse=True):

0 commit comments

Comments
 (0)