Technical deep-dives on build systems, toolchains, and cross-platform development
Choosing the Right IDE for Large-Scale C++ Development
When working on large C++ monorepos with Bazel, IDE choice significantly impacts productivity. This post compares VS Code (with clangd) and CLion (with the Bazel plugin) for real-world Bazel development—covering setup, performance, and daily workflows.
| Aspect | VS Code + clangd | CLion + Bazel Plugin |
|---|---|---|
| Cost | Free | $249/year (or free for OSS) |
| Setup Complexity | Medium | Low |
| Index Speed | Fast (clangd) | Slower (full project sync) |
| Memory Usage | ~2-4GB | ~4-8GB+ |
| Refactoring | Basic | Advanced |
| Debugging | Via CodeLLDB | Native, excellent |
| Bazel Integration | Manual (compile_commands.json) | Native plugin |
| Remote Dev | Excellent (SSH, containers) | Good (Gateway) |
VS Code with clangd provides fast, lightweight C++ intelligence by leveraging compile_commands.json generated from Bazel.
- clangd (llvm-vs-code-extensions.vscode-clangd)
- CodeLLDB (for debugging)
- Bazel (optional, for BUILD file syntax)
Use hedron’s compile-commands-extractor:
# MODULE.bazel
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
git_override(
module_name = "hedron_compile_commands",
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
commit = "4f28899228fb3ad0126897876f147ca15026151e",
)
# BUILD.bazel (workspace root)
load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands")
refresh_compile_commands(
name = "refresh_compile_commands",
targets = {
"//src/...": "",
"//lib/...": "",
},
)
Generate and refresh:
bazel run //:refresh_compile_commands
Create .clangd in your workspace root:
CompileFlags:
Add:
- "-Wno-unknown-warning-option"
Remove:
- "-fno-canonical-system-headers"
InlayHints:
Enabled: true
ParameterNames: true
DeducedTypes: true
Diagnostics:
UnusedIncludes: Strict
{
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--header-insertion=iwyu",
"--completion-style=detailed",
"-j=8"
],
"clangd.path": "/usr/bin/clangd",
"files.watcherExclude": {
"**/bazel-*/**": true
}
}
Issue: “argument unused during compilation: ‘-c’“
This warning appears when clangd queries headers. Fixed in PR #276.
Issue: Cross-platform build fails with missing toolchain
When using --platforms for cross-compilation, refresh_compile_commands fails. Fixed by adding manual tag in PR #276.
Issue: Headers not found after adding new files
Re-run bazel run //:refresh_compile_commands to regenerate the compile commands.
CLion with the official Bazel plugin provides deep integration but requires more resources.
Settings → Plugins → Marketplace → "Bazel for CLion"
File → Import Bazel Projectdirectories:
src
lib
include
targets:
//src/...
//lib/...
additional_languages:
python
build_flags:
--config=clang
Bazel → Sync Project with BUILD Files
This can take 10-30+ minutes on large monorepos.
# Help → Edit Custom VM Options
-Xms2g
-Xmx8g
-XX:+UseG1GC
-XX:ReservedCodeCacheSize=512m
Reduce sync scope in .bazelproject:
# Only sync what you're actively working on
targets:
//src/myproject/...
-//src/myproject/tests/...
| Feature | VS Code + clangd | CLion |
|---|---|---|
| Go to Definition | ✅ Fast | ✅ Fast |
| Find Usages | ✅ Good | ✅ Excellent (cross-language) |
| Call Hierarchy | ✅ | ✅ |
| Type Hierarchy | ❌ Limited | ✅ |
| Include Hierarchy | ✅ | ✅ |
| Feature | VS Code + clangd | CLion |
|---|---|---|
| Rename | ✅ | ✅ Better cross-file |
| Extract Function | ❌ | ✅ |
| Extract Variable | ❌ | ✅ |
| Change Signature | ❌ | ✅ |
| Move to File | ❌ | ✅ |
| Feature | VS Code (CodeLLDB) | CLion |
|---|---|---|
| Breakpoints | ✅ | ✅ |
| Conditional Breakpoints | ✅ | ✅ |
| Watch Expressions | ✅ | ✅ |
| Memory View | ❌ | ✅ |
| Disassembly | ✅ Limited | ✅ Excellent |
| Bazel Target Debug | Manual setup | ✅ Native |
| Feature | VS Code | CLion |
|---|---|---|
| BUILD Syntax | Extension | ✅ Native |
| Run Targets | Terminal | ✅ UI |
| Debug Targets | Manual | ✅ One-click |
| Query/cquery | Terminal | ✅ UI |
| Build on Save | Manual | ✅ Configurable |
Many teams use both:
Both can coexist—VS Code uses compile_commands.json while CLion uses its own index.
#!/bin/bash
# .git/hooks/post-merge
if git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD | grep -qE '(BUILD|\.bzl)$'; then
echo "BUILD files changed, refreshing compile_commands.json..."
bazel run //:refresh_compile_commands 2>/dev/null &
fi
# .github/workflows/ide.yml
name: IDE Config Validation
on: [pull_request]
jobs:
compile-commands:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate compile_commands.json
run: bazel run //:refresh_compile_commands
- name: Verify no errors
run: |
# Check clangd can parse the compilation database
clangd --check=src/main.cpp --log=error
| Scenario | Recommendation |
|---|---|
| Large monorepo, many contributors | CLion (standardization) |
| Small team, fast iteration | VS Code (lightweight) |
| Remote development | VS Code (excellent SSH) |
| Heavy debugging | CLion (native debugger) |
| Cost-sensitive | VS Code (free) |
| Mixed language (C++/Java/Python) | CLion (JetBrains ecosystem) |
Both setups work well with Bazel. The key is generating accurate compile_commands.json for VS Code or maintaining a focused .bazelproject for CLion.
Related Posts:
Tools Referenced: