Perform periodic maintenance on Ninja dependency database. Runs ``ninja -t recompact`` to optimize the ``.ninja_deps`` log. Three markers govern behavior: - ``.ninja_deps_maintenance``: 24h cooldown timestamp (touched on every attempt, regardless of outcome). - ``.ninja_
(build_dir: Path)
| 350 | |
| 351 | |
| 352 | def perform_ninja_maintenance(build_dir: Path) -> bool: |
| 353 | """ |
| 354 | Perform periodic maintenance on Ninja dependency database. |
| 355 | |
| 356 | Runs ``ninja -t recompact`` to optimize the ``.ninja_deps`` log. Three |
| 357 | markers govern behavior: |
| 358 | |
| 359 | - ``.ninja_deps_maintenance``: 24h cooldown timestamp (touched on every |
| 360 | attempt, regardless of outcome). |
| 361 | - ``.ninja_deps_recompact_broken``: sentinel set when recompact has been |
| 362 | observed to fail in this build dir. Once present, recompact is skipped |
| 363 | entirely on subsequent calls — it is an optimization, not a correctness |
| 364 | requirement. |
| 365 | - ``.ninja_deps.bak``: snapshot of the original deps log taken before |
| 366 | recompact runs. Restored if recompact fails so the build dir keeps its |
| 367 | incremental header→TU dependency information. Deleted on success. |
| 368 | |
| 369 | Why the snapshot+restore + broken-marker strategy: on Windows, a |
| 370 | partially-written ``.ninja_deps`` (left by a build killed mid-write) |
| 371 | triggers a recursive crash in ninja's recompact tool (upstream |
| 372 | https://github.com/ninja-build/ninja/issues/2787). Previous attempts |
| 373 | quarantined the deps log up-front to dodge the crash, but that discarded |
| 374 | valid records once per 24h and silently degraded one build's worth of |
| 375 | incremental rebuilds. Snapshotting + restoring keeps the log intact, and |
| 376 | the broken-marker caps total damage to one cascade per build dir |
| 377 | lifetime instead of one per 24h. |
| 378 | |
| 379 | Args: |
| 380 | build_dir: Meson build directory containing .ninja_deps |
| 381 | |
| 382 | Returns: |
| 383 | True if maintenance was performed or skipped successfully, False on error |
| 384 | """ |
| 385 | marker_file = build_dir / ".ninja_deps_maintenance" |
| 386 | broken_marker = build_dir / ".ninja_deps_recompact_broken" |
| 387 | |
| 388 | # If recompact has been observed broken on this build dir, skip entirely. |
| 389 | if broken_marker.exists(): |
| 390 | return True |
| 391 | |
| 392 | if marker_file.exists(): |
| 393 | try: |
| 394 | last_maintenance = marker_file.stat().st_mtime |
| 395 | time_since_maintenance = time.time() - last_maintenance |
| 396 | hours_since_maintenance = time_since_maintenance / 3600 |
| 397 | if hours_since_maintenance < 24: |
| 398 | return True |
| 399 | except (OSError, IOError): |
| 400 | pass |
| 401 | |
| 402 | # Snapshot .ninja_deps so we can restore it if recompact fails. Use a |
| 403 | # copy (not a rename): recompact reads .ninja_deps in place, and we want |
| 404 | # both the original-in-position and the backup to exist during the run. |
| 405 | # If the snapshot can't be taken, skip recompact entirely — running it |
| 406 | # without a rollback path could leave the deps log in a worse state if |
| 407 | # recompact itself partially succeeds before crashing. |
| 408 | deps_file = build_dir / ".ninja_deps" |
| 409 | deps_bak = build_dir / ".ninja_deps.bak" |
no test coverage detected