Return human-readable strict-path violations in ``compile_commands.json``. Issue #2378 — every ``-I`` / ``-isystem`` / ``-iquote`` / ``-iframework`` / ``-idirafter`` / ``-imsvc`` / ``-include`` / ``-include-pch`` / ``-imacros`` / ``-F`` / ``/I`` path must be absolute, forward-slash, and
(build_dir: Path)
| 206 | |
| 207 | |
| 208 | def find_strict_path_violations(build_dir: Path) -> list[str]: |
| 209 | """Return human-readable strict-path violations in ``compile_commands.json``. |
| 210 | |
| 211 | Issue #2378 — every ``-I`` / ``-isystem`` / ``-iquote`` / ``-iframework`` / |
| 212 | ``-idirafter`` / ``-imsvc`` / ``-include`` / ``-include-pch`` / ``-imacros`` |
| 213 | / ``-F`` / ``/I`` path must be absolute, forward-slash, and free of ``.`` |
| 214 | or ``..`` components. This is the same predicate enforced by |
| 215 | ``ZCCACHE_STRICT_PATHS=absolute`` at compile time; checking it ourselves |
| 216 | after meson setup lets us fail before ninja schedules any compile work. |
| 217 | |
| 218 | Returns an empty list when the file is missing (a fresh tree) or clean. |
| 219 | """ |
| 220 | cc_json = build_dir / "compile_commands.json" |
| 221 | if not cc_json.exists(): |
| 222 | return [] |
| 223 | try: |
| 224 | data = json.loads(cc_json.read_text(encoding="utf-8")) |
| 225 | except (OSError, json.JSONDecodeError): |
| 226 | return [] |
| 227 | if not isinstance(data, list): |
| 228 | return [] |
| 229 | |
| 230 | violations: list[str] = [] |
| 231 | for entry in data: |
| 232 | if not isinstance(entry, dict): |
| 233 | continue |
| 234 | entry_dict = cast(dict[str, object], entry) |
| 235 | command = entry_dict.get("command") |
| 236 | if not isinstance(command, str): |
| 237 | continue |
| 238 | for match in _STRICT_PATH_FLAG_RE.finditer(command): |
| 239 | raw = match.group("path") |
| 240 | normalized = raw.replace("\\", "/") |
| 241 | if "\\" in raw: |
| 242 | violations.append(f"{match.group('prefix').lstrip(chr(34))}{raw}") |
| 243 | continue |
| 244 | if not _is_forward_slash_absolute(normalized): |
| 245 | violations.append(f"{match.group('prefix').lstrip(chr(34))}{raw}") |
| 246 | continue |
| 247 | if _has_dot_components(normalized): |
| 248 | violations.append(f"{match.group('prefix').lstrip(chr(34))}{raw}") |
| 249 | return violations |
| 250 | |
| 251 | |
| 252 | def _enforce_strict_path_violations(build_dir: Path) -> None: |