| """Tool to fix the nvcc's dependecy file output |
| |
| Usage: python nvcc_fix_deps.py nvcc [nvcc args]... |
| |
| This wraps nvcc to ensure that the dependency file created by nvcc with the |
| -MD flag always uses absolute paths. nvcc sometimes outputs relative paths, |
| which ninja interprets as an unresolved dependency, so it triggers a rebuild |
| of that file every time. |
| |
| The easiest way to use this is to define: |
| |
| CMAKE_CUDA_COMPILER_LAUNCHER="python;tools/nvcc_fix_deps.py;ccache" |
| |
| """ |
| |
| from __future__ import annotations |
| |
| import subprocess |
| import sys |
| from pathlib import Path |
| from typing import TextIO |
| |
| |
| def resolve_include(path: Path, include_dirs: list[Path]) -> Path: |
| for include_path in include_dirs: |
| abs_path = include_path / path |
| if abs_path.exists(): |
| return abs_path |
| |
| paths = "\n ".join(str(d / path) for d in include_dirs) |
| raise RuntimeError( |
| f""" |
| ERROR: Failed to resolve dependency: |
| {path} |
| Tried the following paths, but none existed: |
| {paths} |
| """ |
| ) |
| |
| |
| def repair_depfile(depfile: TextIO, include_dirs: list[Path]) -> None: |
| changes_made = False |
| out = "" |
| for line in depfile: |
| if ":" in line: |
| colon_pos = line.rfind(":") |
| out += line[: colon_pos + 1] |
| line = line[colon_pos + 1 :] |
| |
| line = line.strip() |
| |
| if line.endswith("\\"): |
| end = " \\" |
| line = line[:-1].strip() |
| else: |
| end = "" |
| |
| path = Path(line) |
| if not path.is_absolute(): |
| changes_made = True |
| path = resolve_include(path, include_dirs) |
| out += f" {path}{end}\n" |
| |
| # If any paths were changed, rewrite the entire file |
| if changes_made: |
| depfile.seek(0) |
| depfile.write(out) |
| depfile.truncate() |
| |
| |
| PRE_INCLUDE_ARGS = ["-include", "--pre-include"] |
| POST_INCLUDE_ARGS = ["-I", "--include-path", "-isystem", "--system-include"] |
| |
| |
| def extract_include_arg(include_dirs: list[Path], i: int, args: list[str]) -> None: |
| def extract_one(name: str, i: int, args: list[str]) -> str | None: |
| arg = args[i] |
| if arg == name: |
| return args[i + 1] |
| if arg.startswith(name): |
| arg = arg[len(name) :] |
| return arg[1:] if arg[0] == "=" else arg |
| return None |
| |
| for name in PRE_INCLUDE_ARGS: |
| path = extract_one(name, i, args) |
| if path is not None: |
| include_dirs.insert(0, Path(path).resolve()) |
| return |
| |
| for name in POST_INCLUDE_ARGS: |
| path = extract_one(name, i, args) |
| if path is not None: |
| include_dirs.append(Path(path).resolve()) |
| return |
| |
| |
| if __name__ == "__main__": |
| ret = subprocess.run( |
| sys.argv[1:], stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr |
| ) |
| |
| depfile_path = None |
| include_dirs = [] |
| |
| # Parse only the nvcc arguments we care about |
| args = sys.argv[2:] |
| for i, arg in enumerate(args): |
| if arg == "-MF": |
| depfile_path = Path(args[i + 1]) |
| elif arg == "-c": |
| # Include the base path of the cuda file |
| include_dirs.append(Path(args[i + 1]).resolve().parent) |
| else: |
| extract_include_arg(include_dirs, i, args) |
| |
| if depfile_path is not None and depfile_path.exists(): |
| with depfile_path.open("r+") as f: |
| repair_depfile(f, include_dirs) |
| |
| sys.exit(ret.returncode) |