Check if ninja invocation can be skipped (stdlib only, no ci.* imports). Verifies that: 1. Cached state file exists and is valid 2. build.ninja hasn't been modified 3. Source files (src/ and tests/) haven't been modified 4. Target output file hasn't been modified Mi
(
build_dir: Path,
target: str,
narrow_test_dir: Path | None = None,
)
| 146 | |
| 147 | |
| 148 | def ninja_skip( |
| 149 | build_dir: Path, |
| 150 | target: str, |
| 151 | narrow_test_dir: Path | None = None, |
| 152 | ) -> bool: |
| 153 | """Check if ninja invocation can be skipped (stdlib only, no ci.* imports). |
| 154 | |
| 155 | Verifies that: |
| 156 | 1. Cached state file exists and is valid |
| 157 | 2. build.ninja hasn't been modified |
| 158 | 3. Source files (src/ and tests/) haven't been modified |
| 159 | 4. Target output file hasn't been modified |
| 160 | |
| 161 | Mirrors ci/meson/cache_utils.check_ninja_skip() without importing that module. |
| 162 | |
| 163 | Args: |
| 164 | build_dir: Meson build directory. |
| 165 | target: Meson target name (e.g. "fl_alloca"). |
| 166 | narrow_test_dir: If provided, narrows the tests/ scan to: |
| 167 | - tests/ root (flat, no recursion) for shared headers like test.h |
| 168 | - narrow_test_dir (recursive) for target-specific sources |
| 169 | This reduces scan cost when running a specific test (~78% fewer files). |
| 170 | |
| 171 | Returns: |
| 172 | True if ninja build can be skipped (no source changes detected), |
| 173 | False if rebuild is needed. |
| 174 | """ |
| 175 | state_file = build_dir / ".ninja_skip_state.json" |
| 176 | if not state_file.exists(): |
| 177 | return False |
| 178 | try: |
| 179 | with open(state_file, "r", encoding="utf-8") as f: |
| 180 | saved = json.load(f) |
| 181 | build_ninja = build_dir / "build.ninja" |
| 182 | if not build_ninja.exists(): |
| 183 | return False |
| 184 | if ( |
| 185 | abs(build_ninja.stat().st_mtime - saved.get("build_ninja_mtime", -1.0)) |
| 186 | > 0.001 |
| 187 | ): |
| 188 | return False |
| 189 | # Check meson.build files that affect build configuration. |
| 190 | # Changes to these files require meson reconfigure + rebuild. |
| 191 | _meson_files = [ |
| 192 | Path("meson.build"), |
| 193 | Path("tests/meson.build"), |
| 194 | Path("ci/meson/native/meson.build"), |
| 195 | Path("ci/meson/wasm/meson.build"), |
| 196 | Path("examples/meson.build"), |
| 197 | ] |
| 198 | saved_meson_max = saved.get("meson_max_file_mtime", -1.0) |
| 199 | _meson_max = 0.0 |
| 200 | for mf in _meson_files: |
| 201 | try: |
| 202 | _meson_max = max(_meson_max, mf.stat().st_mtime) |
| 203 | except OSError: |
| 204 | pass |
| 205 | if _meson_max > saved_meson_max + 0.001: |
no test coverage detected