Check if the ninja invocation can be skipped for this target. Returns True if all of these hold: 1. build.ninja mtime matches saved state (no meson reconfiguration) 2. src/ max source file mtime <= saved value (no src/ code changes) 3. tests/ max source file mtime <= saved value (no
(build_dir: Path, target: str)
| 152 | |
| 153 | |
| 154 | def check_ninja_skip(build_dir: Path, target: str) -> bool: |
| 155 | """Check if the ninja invocation can be skipped for this target. |
| 156 | |
| 157 | Returns True if all of these hold: |
| 158 | 1. build.ninja mtime matches saved state (no meson reconfiguration) |
| 159 | 2. src/ max source file mtime <= saved value (no src/ code changes) |
| 160 | 3. tests/ max source file mtime <= saved value (no test source modifications) |
| 161 | 4. The target's output file exists with the same mtime as when last built |
| 162 | |
| 163 | Overhead: ~20-50ms (scandir over src/ + tests/ source files + stat calls + JSON read). |
| 164 | Savings when fired: ~2-3s (skips the full ninja startup + dependency graph load). |
| 165 | |
| 166 | Correctness: |
| 167 | - test source edits (alloca.cpp modified): detected via tests/ file mtime scan |
| 168 | - src/ code changes: detected via src/ file mtime scan (not libfastled.a proxy, |
| 169 | because libfastled.a is only updated AFTER ninja runs) |
| 170 | - meson reconfiguration: detected via build.ninja mtime |
| 171 | - external DLL modification: detected via output file mtime check |
| 172 | """ |
| 173 | state_file = _get_ninja_skip_state_file(build_dir) |
| 174 | if not state_file.exists(): |
| 175 | return False |
| 176 | |
| 177 | try: |
| 178 | with open(state_file, "r", encoding="utf-8") as f: |
| 179 | saved = json.load(f) |
| 180 | |
| 181 | # 1. Check build.ninja mtime (detects meson reconfiguration) |
| 182 | build_ninja = build_dir / "build.ninja" |
| 183 | if not build_ninja.exists(): |
| 184 | return False |
| 185 | bn_mtime = build_ninja.stat().st_mtime |
| 186 | if abs(bn_mtime - saved.get("build_ninja_mtime", -1.0)) > 0.001: |
| 187 | return False |
| 188 | |
| 189 | # 2a. Check meson.build files (detects build config changes) |
| 190 | cwd = Path.cwd() |
| 191 | _meson_files = [ |
| 192 | cwd / "meson.build", |
| 193 | cwd / "tests" / "meson.build", |
| 194 | cwd / "ci" / "meson" / "native" / "meson.build", |
| 195 | cwd / "ci" / "meson" / "wasm" / "meson.build", |
| 196 | cwd / "examples" / "meson.build", |
| 197 | ] |
| 198 | _meson_max = 0.0 |
| 199 | for mf in _meson_files: |
| 200 | try: |
| 201 | _meson_max = max(_meson_max, mf.stat().st_mtime) |
| 202 | except OSError: |
| 203 | pass |
| 204 | if _meson_max > saved.get("meson_max_file_mtime", -1.0) + 0.001: |
| 205 | return False # meson.build file modified since last build |
| 206 | |
| 207 | # 2b. Check src/ max source file mtime (detects src/ code changes). |
| 208 | # Previously used libfastled.a mtime as a proxy, but libfastled.a is only |
| 209 | # updated AFTER ninja runs. Since this check fires BEFORE ninja, we must |
| 210 | # scan src/ files directly to detect content modifications. |
| 211 | src_max_file_mtime = _get_max_source_file_mtime(cwd / "src") |
no test coverage detected