| """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" | |
| """ | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from typing import List, Optional, 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.readlines(): | |
| 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]) -> Optional[str]: | |
| 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) |