Scan all src/fl/ headers in parallel and return {file: [violations]}. Args: quiet: Suppress progress output. cache: Optional IWYUCache for per-file result caching.
(
quiet: bool = False, cache: IWYUCache | None = None
)
| 230 | |
| 231 | |
| 232 | def scan_fl_headers( |
| 233 | quiet: bool = False, cache: IWYUCache | None = None |
| 234 | ) -> dict[str, list[str]]: |
| 235 | """Scan all src/fl/ headers in parallel and return {file: [violations]}. |
| 236 | |
| 237 | Args: |
| 238 | quiet: Suppress progress output. |
| 239 | cache: Optional IWYUCache for per-file result caching. |
| 240 | """ |
| 241 | fl_dir = _PROJECT_ROOT / "src" / "fl" |
| 242 | files: list[Path] = [] |
| 243 | for ext in ("*.h", "*.hpp"): |
| 244 | files.extend(fl_dir.rglob(ext)) |
| 245 | files = [f for f in files if not str(f).endswith(".cpp.hpp")] |
| 246 | files = sorted(files) |
| 247 | |
| 248 | results: dict[str, list[str]] = {} |
| 249 | |
| 250 | # --- Phase 0: compute source tree hash for dependency-aware caching --- |
| 251 | if cache is not None: |
| 252 | tree_hash = compute_source_tree_hash(files) |
| 253 | cache.set_source_tree_hash(tree_hash) |
| 254 | |
| 255 | # --- Phase 1: check cache for each file --- |
| 256 | uncached_files: list[Path] = [] |
| 257 | if cache is not None: |
| 258 | for f in files: |
| 259 | cached = cache.get(f, _COMPILER_ARGS_KEY) |
| 260 | if cached is not None: |
| 261 | # Cache hit — use stored result |
| 262 | if cached: # non-empty removals |
| 263 | rel = str(f.relative_to(_PROJECT_ROOT)).replace("\\", "/") |
| 264 | results[rel] = cached |
| 265 | else: |
| 266 | uncached_files.append(f) |
| 267 | else: |
| 268 | uncached_files = list(files) |
| 269 | |
| 270 | max_workers = os.cpu_count() or 4 |
| 271 | if not quiet: |
| 272 | cache_info = "" |
| 273 | if cache is not None: |
| 274 | cached_count = len(files) - len(uncached_files) |
| 275 | cache_info = f" ({cached_count} cached, {len(uncached_files)} to scan)" |
| 276 | print( |
| 277 | f"Scanning {len(files)} headers with {max_workers} workers...{cache_info}" |
| 278 | ) |
| 279 | start = time.time() |
| 280 | |
| 281 | # --- Phase 2: run IWYU on cache misses only --- |
| 282 | done = 0 |
| 283 | if uncached_files: |
| 284 | with ProcessPoolExecutor(max_workers=max_workers) as pool: |
| 285 | futures = {pool.submit(_scan_one_header, str(f)): f for f in uncached_files} |
| 286 | for future in as_completed(futures): |
| 287 | done += 1 |
| 288 | if not quiet and done % 50 == 0: |
| 289 | elapsed = time.time() - start |
no test coverage detected