Skip to content

Commit b6e1222

Browse files
authored
feat: add simple compile_commands.json generation (#7)
This implementation is oversimplified on purpose. The proper implementation would use aspects and build command line arguments from features and flags, and be able to integrate with various tools or IDEs.
1 parent 6dbf7ba commit b6e1222

File tree

11 files changed

+115
-4
lines changed

11 files changed

+115
-4
lines changed

cpp/extension.bzl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
load("//cpp:repositories.bzl", "download_llvm")
1+
load("//cpp:repositories.bzl", "download_llvm", "setup_tools")
22

33
def _init_toolchain(ctx):
44
for mod in ctx.modules:
55
for llvm in mod.tags.llvm:
66
download_llvm(ctx, llvm.name, llvm.version)
7+
for tools in mod.tags.tools:
8+
setup_tools(ctx, tools.name)
79

810
_llvm = tag_class(
911
attrs = {
@@ -12,7 +14,13 @@ _llvm = tag_class(
1214
},
1315
)
1416

17+
_tools = tag_class(
18+
attrs = {
19+
"name": attr.string(),
20+
}
21+
)
22+
1523
cpp = module_extension(
1624
implementation = _init_toolchain,
17-
tag_classes = {"llvm": _llvm},
25+
tag_classes = {"llvm": _llvm, "tools": _tools},
1826
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import subprocess
2+
import re
3+
import os
4+
import json
5+
6+
7+
def get_bazel_root():
8+
info = subprocess.run(["bazel info"], shell=True, capture_output=True, text=True).stderr
9+
return re.search(r"The pertinent workspace directory is: \'(.*)\'", info).group(1)
10+
11+
def parse_actions(actions):
12+
actions = actions.split("\n\n")
13+
14+
mappings = []
15+
16+
for action in actions:
17+
if action == "":
18+
continue
19+
#print(action)
20+
#print(re.search(r"Inputs: \[(.*)\]", action))
21+
sources = []
22+
for src in re.search(r"Inputs: \[(.*)\]", action).group(1).split(","):
23+
if src.endswith('.cpp') or src.endswith('.c'):
24+
sources.append(src.strip())
25+
prefix = "Command Line: (exec "
26+
command_line = action[action.find(prefix) + len(prefix):]
27+
command_line = command_line[:command_line.find(")\n")]
28+
command_line = command_line.replace("\\\n", "")
29+
30+
for src in sources:
31+
command_line = command_line.replace(src, "")
32+
33+
command_line = command_line.split()
34+
35+
for src in sources:
36+
mappings.append({"file": src, "arguments": command_line})
37+
38+
return mappings
39+
40+
41+
def save_compile_commands(mappings):
42+
compile_commands = []
43+
44+
for m in mappings:
45+
compile_commands.append({"directory": os.getcwd(), "file": m['file'], "arguments": m['arguments']})
46+
47+
with open('compile_commands.json', 'w', encoding='utf-8') as f:
48+
json.dump(compile_commands, f, indent=2)
49+
50+
51+
base_path = get_bazel_root()
52+
os.chdir(base_path)
53+
actions = subprocess.run(["bazel aquery 'mnemonic(\"CppCompile\", (inputs(\".*cpp\", //...)))'"], shell=True, capture_output=True, text=True).stdout
54+
mappings = parse_actions(actions)
55+
save_compile_commands(mappings)

cpp/private/tools.BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
load("//:tools_rules.bzl", "refresh_compile_commands")
2+
3+
refresh_compile_commands(
4+
name = "refresh_compile_commands"
5+
)

cpp/private/tools_rules.bzl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
def refresh_compile_commands(name):
2+
native.py_binary(
3+
name = name,
4+
main = "refresh_compile_commands.py",
5+
srcs = [
6+
"refresh_compile_commands.py",
7+
],
8+
imports = [''],
9+
)

cpp/repositories.bzl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,22 @@ def download_llvm(mctx, repo_name, version):
3737
sha256 = clang_repo[version]["sha256"],
3838
build_file = "//cpp/private:llvm.BUILD",
3939
)
40+
41+
def _tools_impl(rctx):
42+
rctx.symlink(Label("//cpp/private:refresh_compile_commands.py"), "refresh_compile_commands.py")
43+
# rctx.symlink("//cpp/private:tools.BUILD", "BUILD.bazel")
44+
rctx.symlink(Label("//cpp/private:tools_rules.bzl"), "tools_rules.bzl")
45+
build = rctx.read(Label("//cpp/private:tools.BUILD"))
46+
rctx.file("WORKSPACE", executable=False)
47+
rctx.file("BUILD", content=build)
48+
49+
_tools = repository_rule(
50+
implementation=_tools_impl,
51+
configure=True,
52+
# local=True,
53+
)
54+
55+
def setup_tools(mctx, repo_name):
56+
_tools(
57+
name = repo_name,
58+
)

integration_tests/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.lock
2+
compile_commands.json

integration_tests/toolchains/BUILD.bazel

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ cc_library(
1313
cc_binary(
1414
name = "test",
1515
srcs = ["src/main.cpp"],
16-
deps = [":foo"],
16+
deps = [
17+
":foo",
18+
"//lib2",
19+
],
1720
)

integration_tests/toolchains/MODULE.bazel

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ cpp.llvm(
1414
name = "llvm-17",
1515
version = "17.0.6",
1616
)
17-
use_repo(cpp, "llvm-17")
17+
cpp.tools(
18+
name = "cpp_tools",
19+
)
20+
use_repo(cpp, "cpp_tools", "llvm-17")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cc_library(
2+
name = "lib2",
3+
srcs = ["foo.cpp"],
4+
visibility = ["//visibility:public"],
5+
)

integration_tests/toolchains/lib2/foo.cpp

Whitespace-only changes.

0 commit comments

Comments
 (0)