Skip to content

Commit d1eb12c

Browse files
Add tooling and documentation for setting up clangd (#9137)
Co-authored-by: Zhanyong Wan <[email protected]>
1 parent 41c9913 commit d1eb12c

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed

.clangd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CompileFlags:
2+
CompilationDatabase: build # Specifies that compile_commands.json is in this directory.

CONTRIBUTING.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,27 @@ commands on your Linux machine directly, outside of the container.
170170
# Output: xla:0
171171
```
172172

173+
1. Set up `clangd` so that C++ code completion/navigation works:
174+
175+
1. Install `clangd`: open any C++ source file in VS Code to trigger a
176+
prompt to install `clangd` in the dev container. Accept the request.
177+
Restart VS Code for the change to take effect.
178+
179+
1. Generate the compilation database so that `clangd` knows how to compile
180+
the C++ files:
181+
182+
```bash
183+
# Run this from a terminal in VS Code, in the pytorch/xla directory
184+
# of the workspace.
185+
scripts/update_compile_commands.py
186+
```
187+
188+
This should create the `build/compile_commands.json` file, which
189+
describes how each C++ source file is compiled. The script may take
190+
several minutes the first time. You may need to rerun the script
191+
if build rules or file structures have changed. However, subsequent
192+
runs are usually much faster.
193+
173194
**Subsequent builds**: after building the packages from source code for the
174195
first time, you may need to build everything again, for example, after a
175196
`git pull`. You can run `scripts/build_developer.sh` which will rebuild PyTorch,

external

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bazel-xla/external

scripts/update_compile_commands.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python3
2+
"""Updates build/compile_commands.json for clangd to index C++ files.
3+
4+
USAGE:
5+
scripts/update_compile_commands.py
6+
"""
7+
8+
import dataclasses
9+
import os
10+
import sys
11+
import subprocess
12+
import json
13+
14+
from typing import Optional
15+
16+
# The implementation is largely borrowed from OpenXLA, with some usability improvements
17+
# and fixes specific to PyTorch/XLA.
18+
19+
_JSONDict = dict[str, str] # Approximates parsed JSON
20+
21+
# The repo root directory.
22+
_REPO_ROOT: str = os.path.abspath(os.path.dirname(__file__) + '/..')
23+
24+
# Where to write the compile_commands.json file.
25+
_COMPILE_DB_PATH: str = os.path.join(_REPO_ROOT, 'build/compile_commands.json')
26+
27+
# Flags to filter from the C++ compiler commandline.
28+
_DISALLOWED_ARGS = frozenset(["-fno-canonical-system-headers"])
29+
30+
31+
@dataclasses.dataclass
32+
class CompileCommand:
33+
"""Represents a compilation command with options on a specific C++ file."""
34+
35+
file: str
36+
arguments: list[str]
37+
38+
@classmethod
39+
def from_args_list(cls, args_list: list[str]) -> Optional["CompileCommand"]:
40+
"""Alternative constructor which uses the args_list from `bazel aquery`.
41+
42+
This collects arguments and the file being run on from the output of
43+
`bazel aquery`. Also filters out arguments which break clang-tidy.
44+
45+
Arguments:
46+
args_list: List of arguments generated by `bazel aquery`
47+
48+
Returns:
49+
The corresponding ClangTidyCommand.
50+
"""
51+
cc_file = None
52+
filtered_args = []
53+
54+
for arg in args_list:
55+
if arg in _DISALLOWED_ARGS:
56+
continue
57+
58+
last_dot_index = arg.rfind('.')
59+
if last_dot_index >= 0:
60+
extension = arg[last_dot_index + 1:]
61+
if extension in ("cc", "cpp", "c"):
62+
cc_file = arg
63+
64+
filtered_args.append(arg)
65+
66+
if cc_file:
67+
return cls(cc_file, filtered_args)
68+
69+
return None
70+
71+
def to_dumpable_json(self, directory: str) -> _JSONDict:
72+
return {
73+
"directory": directory,
74+
"file": self.file,
75+
"arguments": self.arguments,
76+
}
77+
78+
79+
def extract_cc_compile_commands(
80+
parsed_aquery_output: _JSONDict,) -> list[CompileCommand]:
81+
"""Gathers C++ compile commands to run from `bazel aquery` JSON output.
82+
83+
Arguments:
84+
parsed_aquery_output: Parsed JSON representing the output of `bazel aquery
85+
--output=jsonproto`.
86+
87+
Returns:
88+
The list of CompileCommands for C++ files.
89+
"""
90+
commands = []
91+
for action in parsed_aquery_output["actions"]:
92+
command = CompileCommand.from_args_list(action["arguments"])
93+
if command:
94+
commands.append(command)
95+
return commands
96+
97+
98+
def main() -> None:
99+
if len(sys.argv) != 1:
100+
sys.exit(__doc__)
101+
102+
os.chdir(_REPO_ROOT)
103+
104+
print(
105+
"Rebuilding the repo to ensure that all external repos and "
106+
"generated files are available locally when generating "
107+
"compile_commands.json. This may take several minutes...",
108+
file=sys.stderr)
109+
subprocess.run(['bazel', 'build', '--keep_going', '//...'],
110+
stdout=subprocess.PIPE,
111+
stderr=subprocess.PIPE)
112+
113+
print("Querying bazel for the CppCompile actions...", file=sys.stderr)
114+
aquery_result = subprocess.run([
115+
'bazel', 'aquery', '--keep_going', 'mnemonic(CppCompile, //...)',
116+
'--output=jsonproto'
117+
],
118+
stdout=subprocess.PIPE,
119+
stderr=subprocess.PIPE)
120+
aquery_output = aquery_result.stdout.decode('utf-8')
121+
aquery_json = json.loads(aquery_output)
122+
123+
print(f"Generating {_COMPILE_DB_PATH}...", file=sys.stderr)
124+
commands = extract_cc_compile_commands(aquery_json)
125+
with open(_COMPILE_DB_PATH, "w") as f:
126+
json.dump(
127+
[
128+
command.to_dumpable_json(directory=str(_REPO_ROOT))
129+
for command in commands
130+
],
131+
f,
132+
)
133+
134+
135+
if __name__ == '__main__':
136+
main()

0 commit comments

Comments
 (0)