make clangd work out of the box

- Adds a gen_compile_commands.py script to generate a `compile_commands.json` for clangd, similar to Linux's `scripts/clang-tools/gen_compile_commands.py`
- Configures the dev container to generate the database before the editor is opened
- Adds `clangd` vscode extension to the dev container (and installs `clangd` in the default image)
- Updates CONTRIBUTING.md with instructions for generating a `compile_commands.json` database when *not* using a dev container

Also switches to a less confusing link for the Dev Containers extension.
This commit is contained in:
Ryan El Kochta
2026-07-28 17:28:28 -07:00
parent f38a547aaa
commit beddb0f0ef
10 changed files with 213 additions and 16 deletions
+5 -4
View File
@@ -1,5 +1,3 @@
#!/usr/bin/env bash
# Copyright 2026 The gVisor Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,5 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Running `go version` ensures that the appropriate Go binaries are downloaded.
exec bazel run @io_bazel_rules_go//go version
CompileFlags:
Remove:
# Remove gcc flags that clangd doesn't support
- -fcanonical-system-headers
- -fno-canonical-system-headers
+3 -2
View File
@@ -1,7 +1,7 @@
{
"dockerFile": "images/default/Dockerfile",
"overrideCommand": true,
"postStartCommand": "tools/devcontainer-init-go.sh",
"postStartCommand": "tools/devcontainer-init.sh",
"waitFor": "postStartCommand",
"mounts": [
"source=/var/run/docker.sock,target=/var/run/docker-host.sock,type=bind"
@@ -15,7 +15,8 @@
"vscode": {
"extensions": [
"bazelbuild.vscode-bazel",
"golang.go"
"golang.go",
"llvm-vs-code-extensions.vscode-clangd"
],
"settings": {
"go.goroot": "${workspaceFolder}/bazel-${workspaceFolderBasename}/external/rules_go++go_sdk+main___download_0/"
+3
View File
@@ -9,3 +9,6 @@
/repo.key
# Generate webhook admission.
/test/kubernetes/gvisor-injection-admission-webhook.yaml
# clangd compilation database
/.cache/clangd
/compile_commands.json
+2 -1
View File
@@ -1,6 +1,7 @@
{
"recommendations": [
"bazelbuild.vscode-bazel",
"golang.go"
"golang.go",
"llvm-vs-code-extensions.vscode-clangd"
]
}
+6
View File
@@ -1,4 +1,10 @@
{
"files.associations": {
".clangd": "yaml"
},
"clangd.arguments": [
"--enable-config"
],
"go.toolsEnvVars": {
"GOPACKAGESDRIVER": "${workspaceFolder}/tools/gopackagesdriver.sh"
},
+15 -7
View File
@@ -56,16 +56,15 @@ practices around AI tool usage. This policy includes, but is not limited to:
### Visual Studio Code
The easiest way to work on gVisor in VSCode is using the
[Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers)
extension.
[Dev Containers][vscode-dev-containers] extension.
Open the gVisor repository in VSCode, and a "Folder contains a Dev Container"
popup will appear. Press "Reopen in Container". The first load will take a few
minutes while the dev container is built and the Go packages are loaded.
Afterwards, Go language server features (hover, autocomplete, reference search,
format on save, etc) should work "out of the box". VSCode's terminal will open
in the dev container, where you can run `bazel build`, `bazel test`, and so on
to work on gVisor.
minutes while the dev container is built, the Go packages are loaded, and the
clangd compilation database is generated. Afterwards, language server features
(hover, autocomplete, reference search, format on save, etc) should work "out of
the box" for both Go and C++. VSCode's terminal will open in the dev container,
where you can run `bazel build`, `bazel test`, and so on to work on gVisor.
For example, you can build and run `runsc` in the dev container as follows:
@@ -91,10 +90,17 @@ For non-interactive commands, `make run` can be used as a shorthand as follows:
make run TARGETS=//runsc ARGS="--rootless do echo hello world"
```
In addition, for language server support in C++ files (e.g. the syscall tests):
install [clangd][clangd] on the host, then run `tools/gen_compile_commands.py`
to generate a `compile_commands.json`. After reloading the editor window,
clangd should work.
### Other Editors
For LSP integration in other editors, ensure that the environment variable
`GOPACKAGESDRIVER=tools/gopackagesdriver.sh` is passed along to `gopls`.
Also, ensure `--allow-config` is passed to `clangd`, which will allow gVisor's
`.clangd` configuration to take effect.
#### Using GOPATH (not recommended)
@@ -239,3 +245,5 @@ one above, the
[gvisor-dev-list]: https://groups.google.com/forum/#!forum/gvisor-dev
[go]: https://go.dev/doc/install
[bazelisk]: https://github.com/bazelbuild/bazelisk
[clangd]: https://clangd.llvm.org/installation
[vscode-dev-containers]: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
+2 -2
View File
@@ -14,8 +14,8 @@ RUN apt-get update && apt-get install -y curl gnupg2 git \
apt-transport-https ca-certificates gnupg-agent \
software-properties-common \
pkg-config libffi-dev patch diffutils libssl-dev iptables kmod \
clang crossbuild-essential-amd64 erofs-utils busybox-static libbpf-dev \
iproute2 netcat-openbsd libnuma-dev
clang clangd crossbuild-essential-amd64 erofs-utils busybox-static \
libbpf-dev iproute2 netcat-openbsd libnuma-dev
# This package is needed to build eBPF on amd64, but not on arm64 where it
# doesn't exist.
+49
View File
@@ -0,0 +1,49 @@
# Copyright 2026 The gVisor Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Exposes the generated files a C++ compile reads.
Used by tools/gen_compile_commands.py to avoid compiling the whole test suite
to generate compile_commands.json for clangd.
"""
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
# Source extensions that end up in the compile commands database.
_CC_SRC_EXTENSIONS = (".c", ".cc", ".cpp", ".cxx", ".c++", ".S", ".s", ".inc")
def _cc_compile_inputs_impl(target, ctx):
inputs = []
# Transitively covers every header the target may include, generated ones
# included, plus the _virtual_includes symlink trees that -I flags name.
if CcInfo in target:
inputs.append(target[CcInfo].compilation_context.headers)
# Generated sources are compiled, so they are entries in the database in
# their own right; the header set above does not include them.
inputs.append(depset([
f
for f in getattr(ctx.rule.files, "srcs", [])
if not f.is_source and f.path.endswith(_CC_SRC_EXTENSIONS)
]))
return [OutputGroupInfo(cc_compile_inputs = depset(transitive = inputs))]
# Propagates over deps so that a generated source belonging to a dependency is
# built too; headers alone would already be complete at the top-level target.
cc_compile_inputs = aspect(
implementation = _cc_compile_inputs_impl,
attr_aspects = ["deps"],
)
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Copyright 2026 The gVisor Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Running `go version` ensures that the appropriate Go binaries are downloaded.
echo "====== Fetching Go... ======"
bazel run @io_bazel_rules_go//go version
# Generate a compile_commands.json database so clangd lsp works on C++ tests.
echo "====== Generating compile_commands.json... ======"
python3 tools/gen_compile_commands.py
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
# Copyright 2026 The gVisor Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
def _bazel(*args):
"""Runs bazel, returning its stdout."""
return subprocess.run(
("bazel",) + args, check=True, text=True, stdout=subprocess.PIPE
).stdout
def _compile_actions(targets):
"""Yields the C++ compile actions for the targets' dependencies."""
query = 'mnemonic("CppCompile", deps({}))'.format(" union ".join(targets))
return json.loads(
_bazel("aquery", "--output=jsonproto", "--noshow_progress", query)
)["actions"]
def main():
parser = argparse.ArgumentParser(
description="Generates compile_commands.json for gVisor's C++ code."
)
parser.add_argument(
"targets",
nargs="*",
default=["//test/..."],
help="Bazel targets to index (default: %(default)s).",
)
args = parser.parse_args()
targets = args.targets
workspace = Path(__file__).parent.parent.resolve()
os.chdir(workspace)
_bazel(
"build",
"--aspects=//tools:cc_compile_inputs.bzl%cc_compile_inputs",
"--output_groups=cc_compile_inputs",
*targets,
)
execroot = Path(_bazel("info", "execution_root").strip())
database = []
seen = set()
for action in _compile_actions(targets):
arguments = action["arguments"]
if "-c" not in arguments:
continue
source = Path(arguments[arguments.index("-c") + 1])
# Skip third-party code.
if "external" in source.parts:
continue
# A source can be compiled by several actions.
# Skip all but the first.
if source in seen:
continue
seen.add(source)
# Add the compile command to the database.
database.append(
{
# Special-case for generated files in bazel-out/. When LSP jump is used,
# it will navigate to the execroot path, so match that in the database.
"file": str(
(execroot if source.parts[0] == "bazel-out" else workspace) / source
),
"directory": str(execroot),
"arguments": arguments,
}
)
path = os.path.join(workspace, "compile_commands.json")
with open(path, "w") as out:
json.dump(database, out, indent=4)
print(f"generated {path}: {len(database)} entries")
return 0
if __name__ == "__main__":
raise SystemExit(main())