Save build state after successful ninja run to enable skip on next run. Stores: - build.ninja mtime (gate for meson reconfiguration) - libfastled.a mtime (gate for src/ code changes) - tests/ max source file mtime (gate for test source modifications) - Per-target: output file pa
(build_dir: Path, target: str)
| 236 | |
| 237 | |
| 238 | def save_ninja_skip_state(build_dir: Path, target: str) -> None: |
| 239 | """Save build state after successful ninja run to enable skip on next run. |
| 240 | |
| 241 | Stores: |
| 242 | - build.ninja mtime (gate for meson reconfiguration) |
| 243 | - libfastled.a mtime (gate for src/ code changes) |
| 244 | - tests/ max source file mtime (gate for test source modifications) |
| 245 | - Per-target: output file path + mtime (sanity gate) |
| 246 | |
| 247 | Called only after a successful compilation, so overhead (~30-50ms) is acceptable. |
| 248 | """ |
| 249 | try: |
| 250 | # Derive the output file path from the target name. |
| 251 | # Strip "tests/" prefix if present (meson sometimes uses qualified names). |
| 252 | stem = target.removeprefix("tests/") |
| 253 | is_windows = platform.system() == "Windows" |
| 254 | ext = ".dll" if is_windows else ".so" |
| 255 | output_path = build_dir / "tests" / f"{stem}{ext}" |
| 256 | if not output_path.exists(): |
| 257 | # Fall back to .exe for special targets (runner, profile tests) |
| 258 | exe_path = build_dir / "tests" / f"{stem}.exe" |
| 259 | if exe_path.exists(): |
| 260 | output_path = exe_path |
| 261 | else: |
| 262 | return # Can't determine output file — don't save state |
| 263 | |
| 264 | state_file = _get_ninja_skip_state_file(build_dir) |
| 265 | |
| 266 | # Load existing state to preserve other targets |
| 267 | existing: dict = {} |
| 268 | if state_file.exists(): |
| 269 | try: |
| 270 | with open(state_file, "r", encoding="utf-8") as f: |
| 271 | existing = json.load(f) |
| 272 | except (json.JSONDecodeError, OSError): |
| 273 | existing = {} |
| 274 | |
| 275 | # Update global build-state gates |
| 276 | build_ninja = build_dir / "build.ninja" |
| 277 | cwd = Path.cwd() |
| 278 | existing["build_ninja_mtime"] = ( |
| 279 | build_ninja.stat().st_mtime if build_ninja.exists() else 0.0 |
| 280 | ) |
| 281 | |
| 282 | # src/ max source file mtime: detects src/ code changes directly. |
| 283 | # Previously used libfastled.a as a proxy, but file-level scanning |
| 284 | # is needed because the skip check fires BEFORE ninja rebuilds libfastled.a. |
| 285 | existing["src_max_file_mtime"] = _get_max_source_file_mtime(cwd / "src") |
| 286 | |
| 287 | # tests/ max source file mtime: detects test source modifications |
| 288 | existing["tests_max_file_mtime"] = _get_max_source_file_mtime(cwd / "tests") |
| 289 | |
| 290 | # meson.build max mtime: detects build configuration changes |
| 291 | _meson_files = [ |
| 292 | cwd / "meson.build", |
| 293 | cwd / "tests" / "meson.build", |
| 294 | cwd / "ci" / "meson" / "native" / "meson.build", |
| 295 | cwd / "ci" / "meson" / "wasm" / "meson.build", |
no test coverage detected