Skip to content

Commit 62d5bd0

Browse files
authored
Add local Claude Code gopls plugin with build tags support (#48901)
### What does this PR do? Adds a repo-local Claude Code gopls LSP plugin that passes the correct Go build tags and the `-buildvcs=false` performance flag to `gopls`. This replaces the need for the external `gopls-lsp@claude-plugins-official` plugin which doesn't support build tags. **New files:** - `tasks/claude.py` — invoke task `dda inv claude.set-buildtags` to generate/update the plugin configuration, following the same pattern as `emacs.set-buildtags` and `vim.set-buildtags` - `.claude-plugins/` — local marketplace with the `datadog-agent-gopls` plugin definition and a `SessionStart` hook to auto-refresh build tags on each session - `.claude/settings.json` — enables the local plugin for all contributors ### Motivation The `datadog-agent` repo uses Go build tags extensively. Without proper tags, `gopls` ignores most files, making the LSP largely useless. Each editor already has an invoke task for this (`dda inv vscode.set-buildtags`, `dda inv emacs.set-buildtags`, `dda inv vim.set-buildtags`), but Claude Code had no equivalent. The official marketplace `gopls-lsp` plugin doesn't support specifying build tags. A prototype was developed externally in `DataDog/claude-marketplace` (PR #938), but since this plugin is specific to `datadog-agent`, it belongs in the repo itself. ### Describe how you validated your changes - `dda inv claude.set-buildtags` runs successfully and produces valid JSON with 43 sorted build tags, `-buildvcs=false`, and `formatting.local` - `dda inv claude.set-buildtags --targets=agent` works correctly with a subset of tags - All generated JSON files pass validation ### Additional Notes Contributors using the external `datadog-agent-gopls@datadog-claude-plugins-dev` plugin in their `settings.local.json` should disable it to avoid running two `gopls` instances. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: lenaic.huard <lenaic.huard@datadoghq.com>
1 parent 2a058f3 commit 62d5bd0

7 files changed

Lines changed: 220 additions & 0 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "datadog-agent-local",
3+
"metadata": {
4+
"description": "Local plugins for the datadog-agent repository"
5+
},
6+
"owner": {
7+
"name": "Datadog Agent DevX",
8+
"email": "agent-devx@datadoghq.com"
9+
},
10+
"plugins": [
11+
{
12+
"name": "datadog-agent-gopls",
13+
"source": "./datadog-agent-gopls"
14+
}
15+
]
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "datadog-agent-gopls",
3+
"version": "1.0.0",
4+
"description": "Go LSP (gopls) pre-configured for the datadog-agent repository with build tags and performance flags",
5+
"author": {
6+
"name": "L\u00e9na\u00efc Huard",
7+
"email": "lenaic.huard@datadoghq.com"
8+
},
9+
"lspServers": {
10+
"gopls": {
11+
"command": "gopls",
12+
"extensionToLanguage": {
13+
".go": "go"
14+
},
15+
"initializationOptions": {
16+
"build.buildFlags": [
17+
"-tags=cel,clusterchecks,consul,containerd,cri,crio,datadog.no_waf,docker,ec2,etcd,fargateprocess,grpcnotrace,jetson,jmx,kubeapiserver,kubelet,linux_bpf,ncm,netcgo,netgo,no_dynamic_plugins,npm,nvml,oracle,orchestrator,osusergo,otlp,pcap,podman,python,retrynotrace,seclmax,serverless,sharedlibrarycheck,systemd,systemprobechecks,test,trivy,trivy_no_javadb,zk,zlib,zstd",
18+
"-buildvcs=false"
19+
],
20+
"formatting.local": "github.com/DataDog/datadog-agent"
21+
}
22+
}
23+
}
24+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# datadog-agent-gopls
2+
3+
Go LSP (`gopls`) pre-configured for the [DataDog/datadog-agent](https://github.com/DataDog/datadog-agent) repository.
4+
5+
## Why this plugin?
6+
7+
The `datadog-agent` repository uses Go build tags (`//go:build`) on most of its files. Without specifying these tags, `gopls` ignores nearly all source files, making the official `gopls-lsp` plugin unusable for this repository.
8+
9+
This plugin configures `gopls` with:
10+
11+
- **Build tags** covering all major components (generated by `dda inv claude.set-buildtags`)
12+
- **`-buildvcs=false`** to skip VCS info collection, which significantly improves performance in large repositories
13+
- **`formatting.local`** set to `github.com/DataDog/datadog-agent` so that `gopls` groups local imports separately from third-party ones
14+
15+
## Prerequisites
16+
17+
`gopls` must be installed and available in your `PATH`:
18+
19+
```bash
20+
go install golang.org/x/tools/gopls@latest
21+
```
22+
23+
Make sure `$GOPATH/bin` (or `$HOME/go/bin`) is in your `PATH`.
24+
25+
## How it works
26+
27+
The plugin is enabled for all contributors via `.claude/settings.json`, which references the local marketplace in `.claude-plugins/`.
28+
29+
A `SessionStart` hook automatically runs `dda inv claude.set-buildtags` at the beginning of each Claude Code session to keep the build tags up to date.
30+
31+
You can also refresh the tags manually:
32+
33+
```bash
34+
# All targets (default)
35+
dda inv claude.set-buildtags
36+
37+
# Specific target
38+
dda inv claude.set-buildtags --targets=agent
39+
```
40+
41+
This follows the same pattern as `dda inv vscode.set-buildtags`, `dda inv emacs.set-buildtags`, and `dda inv vim.set-buildtags`.
42+
43+
## Disabling the official plugin
44+
45+
If you have the official `gopls-lsp` plugin enabled, disable it in your `.claude/settings.local.json` to avoid running two `gopls` instances:
46+
47+
```json
48+
{
49+
"enabledPlugins": {
50+
"gopls-lsp@claude-plugins-official": false
51+
}
52+
}
53+
```
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"hooks": {
3+
"SessionStart": [
4+
{
5+
"hooks": [
6+
{
7+
"type": "command",
8+
"command": "dda inv claude.set-buildtags 2>&1"
9+
}
10+
]
11+
}
12+
]
13+
}
14+
}

.claude/settings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"enabledPlugins": {
3+
"datadog-agent-gopls@datadog-agent-local": true
4+
},
5+
"extraKnownMarketplaces": {
6+
"datadog-agent-local": {
7+
"source": {
8+
"source": "directory",
9+
"path": ".claude-plugins"
10+
}
11+
}
12+
}
13+
}

tasks/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
auth,
1515
bench,
1616
buildimages,
17+
claude,
1718
cluster_agent,
1819
cluster_agent_cloudfoundry,
1920
collector,
@@ -197,6 +198,7 @@
197198
ns.add_collection(ami)
198199
ns.add_collection(agent_ci_api)
199200
ns.add_collection(buildimages)
201+
ns.add_collection(claude)
200202
ns.add_collection(cluster_agent)
201203
ns.add_collection(cluster_agent_cloudfoundry)
202204
ns.add_collection(components)

tasks/claude.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"""
2+
Claude Code namespaced tags
3+
4+
Helpers for getting Claude Code set up nicely with gopls
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import json
10+
import os
11+
12+
from invoke import task
13+
14+
from tasks.build_tags import build_tags, compute_config_build_tags
15+
from tasks.flavor import AgentFlavor
16+
from tasks.libs.types.version import Version
17+
18+
CLAUDE_PLUGINS_DIR = ".claude-plugins"
19+
PLUGIN_NAME = "datadog-agent-gopls"
20+
PLUGIN_DIR = os.path.join(CLAUDE_PLUGINS_DIR, PLUGIN_NAME, ".claude-plugin")
21+
PLUGIN_FILE = "plugin.json"
22+
DEFAULT_VERSION = "1.0.0"
23+
24+
25+
def _read_existing_plugin() -> tuple[Version, str | None]:
26+
"""Read the existing plugin.json if it exists, returning (version, build_flags_tag)."""
27+
fullpath = os.path.join(PLUGIN_DIR, PLUGIN_FILE)
28+
try:
29+
with open(fullpath) as f:
30+
plugin = json.load(f)
31+
except (FileNotFoundError, json.JSONDecodeError):
32+
return Version.from_tag(DEFAULT_VERSION), None
33+
version = Version.from_tag(plugin.get("version", DEFAULT_VERSION))
34+
flags = plugin.get("lspServers", {}).get("gopls", {}).get("initializationOptions", {}).get("build.buildFlags", [])
35+
tags_flag = next((f for f in flags if f.startswith("-tags=")), None)
36+
return version, tags_flag
37+
38+
39+
@task(
40+
help={
41+
"targets": f"Comma separated list of targets to include. Possible values: all, {', '.join(build_tags[AgentFlavor.base].keys())}. Default: all",
42+
"flavor": f"Agent flavor to use. Possible values: {', '.join(AgentFlavor.__members__.keys())}. Default: {AgentFlavor.base.name}",
43+
}
44+
)
45+
def set_buildtags(
46+
_: object,
47+
targets: str = "all",
48+
build_include: str | None = None,
49+
build_exclude: str | None = None,
50+
flavor: str = AgentFlavor.base.name,
51+
) -> None:
52+
"""
53+
Create/update Claude Code gopls plugin configuration with correct build tags
54+
"""
55+
use_tags = compute_config_build_tags(
56+
targets=targets,
57+
build_include=build_include,
58+
build_exclude=build_exclude,
59+
flavor=flavor,
60+
)
61+
62+
if not os.path.exists(PLUGIN_DIR):
63+
os.makedirs(PLUGIN_DIR)
64+
65+
new_tags_flag = f"-tags={','.join(sorted(use_tags))}"
66+
version, old_tags_flag = _read_existing_plugin()
67+
if old_tags_flag is not None and old_tags_flag != new_tags_flag:
68+
version = version.next_version(bump_patch=True)
69+
70+
plugin = {
71+
"name": PLUGIN_NAME,
72+
"version": str(version),
73+
"description": "Go LSP (gopls) pre-configured for the datadog-agent repository with build tags and performance flags",
74+
"author": {
75+
"name": "Lénaïc Huard",
76+
"email": "lenaic.huard@datadoghq.com",
77+
},
78+
"lspServers": {
79+
"gopls": {
80+
"command": "gopls",
81+
"extensionToLanguage": {
82+
".go": "go",
83+
},
84+
"initializationOptions": {
85+
"build.buildFlags": [
86+
new_tags_flag,
87+
"-buildvcs=false",
88+
],
89+
"formatting.local": "github.com/DataDog/datadog-agent",
90+
},
91+
}
92+
},
93+
}
94+
95+
fullpath = os.path.join(PLUGIN_DIR, PLUGIN_FILE)
96+
with open(fullpath, "w") as f:
97+
json.dump(plugin, f, indent=2)
98+
f.write("\n")

0 commit comments

Comments
 (0)