Skip to content

Commit de929fe

Browse files
authored
Merge pull request #1 from stackb/compdb
Add support for generating clang compilation databases
2 parents cb520a8 + f9735c4 commit de929fe

10 files changed

Lines changed: 707 additions & 38 deletions

File tree

README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
Additional support for [rules_cc](https://github.com/bazelbuild/rules_cc) in
44
conjunction with [bazel-stack-vscode](https://marketplace.visualstudio.com/items?itemName=StackBuild.bazel-stack-vscode)
55

6-
> NOTE: this extension currently depends on an unreleased version of
7-
> `bazel-stack-vscode`. This won't work for you just yet!
8-
96
## Features
107

11-
Problem matcher for rules_cc actions:
8+
### Clang Compilation Database
129

13-
- `CppCompile`
10+
This extension provides a command `Bazel/C++: Generate Compilation Database`
11+
(`bsv.cc.compdb.generate`) that produces a file
12+
`${workspaceDIrectory}/compile_commmands.json`.
1413

15-
### 1.0.0
14+
To setup, edit your workspace settings (search for `bsv.cc.compdb.targets`) and
15+
configure a list of bazel labels for the `cc_binary` or `cc_library` targets
16+
you'd like to be indexed. The tool will then produce a command set for the
17+
transitive closure of those top-level targets.
1618

17-
Initial release.
19+
Works best in conjuction with <https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd>.

compdb/BUILD.bazel

Whitespace-only changes.

compdb/WORKSPACE

Whitespace-only changes.

compdb/aspects.bzl

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
# Copyright 2017 GRAIL, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Compilation database generation Bazel rules.
16+
17+
compilation_database will generate a compile_commands.json file for the
18+
given targets. This approach uses the aspects feature of bazel.
19+
20+
An alternative approach is the one used by the kythe project using
21+
(experimental) action listeners.
22+
https://github.com/google/kythe/blob/master/tools/cpp/generate_compilation_database.sh
23+
"""
24+
25+
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
26+
load(
27+
"@bazel_tools//tools/build_defs/cc:action_names.bzl",
28+
"CPP_COMPILE_ACTION_NAME",
29+
"C_COMPILE_ACTION_NAME",
30+
"OBJCPP_COMPILE_ACTION_NAME",
31+
"OBJC_COMPILE_ACTION_NAME",
32+
)
33+
34+
CompilationAspect = provider()
35+
36+
_cpp_header_extensions = [
37+
"hh",
38+
"hxx",
39+
"ipp",
40+
"hpp",
41+
]
42+
43+
_c_or_cpp_header_extensions = ["h"] + _cpp_header_extensions
44+
45+
_cpp_extensions = [
46+
"cc",
47+
"cpp",
48+
"cxx",
49+
] + _cpp_header_extensions
50+
51+
_cc_rules = [
52+
"cc_library",
53+
"cc_binary",
54+
"cc_test",
55+
"cc_inc_library",
56+
"cc_proto_library",
57+
]
58+
59+
_objc_rules = [
60+
"objc_library",
61+
"objc_binary",
62+
]
63+
64+
_all_rules = _cc_rules + _objc_rules
65+
66+
def _is_cpp_target(srcs):
67+
if all([src.extension in _c_or_cpp_header_extensions for src in srcs]):
68+
return True # assume header-only lib is c++
69+
return any([src.extension in _cpp_extensions for src in srcs])
70+
71+
def _is_objcpp_target(srcs):
72+
return any([src.extension == "mm" for src in srcs])
73+
74+
def _sources(ctx, target):
75+
srcs = []
76+
if hasattr(ctx.rule.attr, "srcs"):
77+
srcs += [f for src in ctx.rule.attr.srcs for f in src.files.to_list()]
78+
if hasattr(ctx.rule.attr, "hdrs"):
79+
srcs += [f for src in ctx.rule.attr.hdrs for f in src.files.to_list()]
80+
81+
return srcs
82+
83+
# Function copied from https://gist.github.com/oquenchil/7e2c2bd761aa1341b458cc25608da50c
84+
# TODO: Directly use create_compile_variables and get_memory_inefficient_command_line.
85+
def _get_compile_flags(dep):
86+
options = []
87+
compilation_context = dep[CcInfo].compilation_context
88+
for define in compilation_context.defines.to_list():
89+
options.append("-D\"{}\"".format(define))
90+
91+
for define in compilation_context.local_defines.to_list():
92+
options.append("-D\"{}\"".format(define))
93+
94+
for system_include in compilation_context.system_includes.to_list():
95+
if len(system_include) == 0:
96+
system_include = "."
97+
options.append("-isystem {}".format(system_include))
98+
99+
for include in compilation_context.includes.to_list():
100+
if len(include) == 0:
101+
include = "."
102+
options.append("-I {}".format(include))
103+
104+
for quote_include in compilation_context.quote_includes.to_list():
105+
if len(quote_include) == 0:
106+
quote_include = "."
107+
options.append("-iquote {}".format(quote_include))
108+
109+
for framework_include in compilation_context.framework_includes.to_list():
110+
options.append("-F\"{}\"".format(framework_include))
111+
112+
return options
113+
114+
def _xcode_paths(ctx):
115+
xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]
116+
117+
sdk_version = xcode_config.sdk_version_for_platform(ctx.fragments.apple.single_arch_platform)
118+
apple_env = apple_common.target_apple_env(xcode_config, ctx.fragments.apple.single_arch_platform)
119+
sdk_platform = apple_env["APPLE_SDK_PLATFORM"]
120+
121+
# FIXME is there any way of getting the SDKROOT value here? The only thing that seems to know about it is
122+
# XcodeLocalEnvProvider, but I can't seem to find a way to access that
123+
platform_root = "/Applications/Xcode.app/Contents/Developer/Platforms/{platform}.platform".format(platform = sdk_platform)
124+
sdk_root = "/Applications/Xcode.app/Contents/Developer/Platforms/{platform}.platform/Developer/SDKs/{platform}{version}.sdk".format(platform = sdk_platform, version = sdk_version)
125+
126+
return struct(
127+
platform_root = platform_root,
128+
sdk_root = sdk_root,
129+
)
130+
131+
def _cc_compile_commands(ctx, target, feature_configuration, cc_toolchain):
132+
compiler = str(
133+
cc_common.get_tool_for_action(
134+
feature_configuration = feature_configuration,
135+
action_name = C_COMPILE_ACTION_NAME,
136+
),
137+
)
138+
compile_flags = _get_compile_flags(target)
139+
140+
srcs = _sources(ctx, target)
141+
if ctx.rule.kind == "cc_proto_library":
142+
srcs += [f for f in target.files.to_list() if f.extension in ["h", "cc"]]
143+
144+
# We currently recognize an entire target as C++ or C. This can probably be
145+
# made better for targets that have a mix of C and C++ files.
146+
is_cpp_target = _is_cpp_target(srcs)
147+
148+
compiler_options = None
149+
if is_cpp_target:
150+
compile_variables = cc_common.create_compile_variables(
151+
feature_configuration = feature_configuration,
152+
cc_toolchain = cc_toolchain,
153+
user_compile_flags = ctx.fragments.cpp.cxxopts +
154+
ctx.fragments.cpp.copts,
155+
add_legacy_cxx_options = True,
156+
)
157+
compiler_options = cc_common.get_memory_inefficient_command_line(
158+
feature_configuration = feature_configuration,
159+
action_name = CPP_COMPILE_ACTION_NAME,
160+
variables = compile_variables,
161+
)
162+
compile_flags.append("-x c++") # Force language mode for header files.
163+
else:
164+
compile_variables = cc_common.create_compile_variables(
165+
feature_configuration = feature_configuration,
166+
cc_toolchain = cc_toolchain,
167+
user_compile_flags = ctx.fragments.cpp.copts,
168+
)
169+
compiler_options = cc_common.get_memory_inefficient_command_line(
170+
feature_configuration = feature_configuration,
171+
action_name = C_COMPILE_ACTION_NAME,
172+
variables = compile_variables,
173+
)
174+
175+
compile_flags.extend(ctx.rule.attr.copts if "copts" in dir(ctx.rule.attr) else [])
176+
177+
cmdline_list = [compiler]
178+
cmdline_list.extend(compiler_options)
179+
cmdline_list.extend(compile_flags)
180+
cmdline = " ".join(cmdline_list)
181+
182+
compile_commands = []
183+
for src in srcs:
184+
compile_commands.append(struct(
185+
cmdline = cmdline + " -c " + src.path,
186+
src = src,
187+
))
188+
return compile_commands
189+
190+
def _objc_compile_commands(ctx, target, feature_configuration, cc_toolchain):
191+
compiler = str(
192+
cc_common.get_tool_for_action(
193+
feature_configuration = feature_configuration,
194+
action_name = OBJC_COMPILE_ACTION_NAME,
195+
),
196+
)
197+
compile_flags = _get_compile_flags(target)
198+
199+
srcs = _sources(ctx, target)
200+
201+
non_arc_srcs = []
202+
if "non_arc_srcs" in dir(ctx.rule.attr):
203+
non_arc_srcs += [f for src in ctx.rule.attr.non_arc_srcs for f in src.files.to_list()]
204+
srcs.extend(non_arc_srcs)
205+
206+
# We currently recognize an entire target as objective-c++ or not. This can
207+
# probably be made better for targets that have a mix of files.
208+
is_objcpp_target = _is_objcpp_target(srcs)
209+
210+
compiler_options = None
211+
if is_objcpp_target:
212+
compile_variables = cc_common.create_compile_variables(
213+
feature_configuration = feature_configuration,
214+
cc_toolchain = cc_toolchain,
215+
user_compile_flags = ctx.fragments.objc.copts,
216+
add_legacy_cxx_options = True,
217+
)
218+
compiler_options = cc_common.get_memory_inefficient_command_line(
219+
feature_configuration = feature_configuration,
220+
action_name = OBJCPP_COMPILE_ACTION_NAME,
221+
variables = compile_variables,
222+
)
223+
compile_flags.append("-x objective-c++") # Force language mode for header files.
224+
else:
225+
compile_variables = cc_common.create_compile_variables(
226+
feature_configuration = feature_configuration,
227+
cc_toolchain = cc_toolchain,
228+
user_compile_flags = ctx.fragments.objc.copts,
229+
)
230+
compiler_options = cc_common.get_memory_inefficient_command_line(
231+
feature_configuration = feature_configuration,
232+
action_name = OBJC_COMPILE_ACTION_NAME,
233+
variables = compile_variables,
234+
)
235+
compile_flags.append("-x objective-c") # Force language mode for header files.
236+
237+
frameworks = (
238+
["-F {}/..".format(val) for val in target.objc.static_framework_paths.to_list()] +
239+
["-F {}/..".format(val) for val in target.objc.dynamic_framework_paths.to_list()]
240+
)
241+
compile_flags.extend(frameworks)
242+
243+
compile_flags.extend(ctx.rule.attr.copts if "copts" in dir(ctx.rule.attr) else [])
244+
245+
xcode_paths = _xcode_paths(ctx)
246+
system_flags = [
247+
"-isysroot {}".format(xcode_paths.sdk_root),
248+
"-F {}/System/Library/Frameworks".format(xcode_paths.sdk_root),
249+
"-F {}/Developer/Library/Frameworks".format(xcode_paths.platform_root),
250+
]
251+
252+
cmdline_list = [compiler]
253+
cmdline_list.extend(compiler_options)
254+
cmdline_list.extend(system_flags)
255+
cmdline_list.extend(compile_flags)
256+
cmdline = " ".join(cmdline_list)
257+
258+
compile_commands = []
259+
for src in srcs:
260+
arc_flag = "" if src in non_arc_srcs else " -fobjc-arc"
261+
compile_commands.append(struct(
262+
cmdline = cmdline + arc_flag + " -c " + src.path,
263+
src = src,
264+
))
265+
return compile_commands
266+
267+
def _compilation_database_aspect_impl(target, ctx):
268+
# Write the compile commands for this target to a file, and return
269+
# the commands for the transitive closure.
270+
271+
# Collect any aspects from all transitive dependencies.
272+
# Note that this should also apply to filegroup type targets which may have
273+
# cc_binary targets in their srcs attribute.
274+
deps = []
275+
if hasattr(ctx.rule.attr, "srcs"):
276+
deps.extend(ctx.rule.attr.srcs)
277+
if hasattr(ctx.rule.attr, "deps"):
278+
deps.extend(ctx.rule.attr.deps)
279+
280+
transitive_compilation_db = []
281+
all_compdb_files = []
282+
all_header_files = []
283+
for dep in deps:
284+
if CompilationAspect not in dep:
285+
continue
286+
transitive_compilation_db.append(dep[CompilationAspect].compilation_db)
287+
all_compdb_files.append(dep[OutputGroupInfo].compdb_files)
288+
all_header_files.append(dep[OutputGroupInfo].header_files)
289+
290+
# We support only these rule kinds.
291+
if ctx.rule.kind not in _all_rules:
292+
return [
293+
CompilationAspect(compilation_db = depset(transitive = transitive_compilation_db)),
294+
OutputGroupInfo(
295+
compdb_files = depset(transitive = all_compdb_files),
296+
header_files = depset(transitive = all_header_files),
297+
direct_src_files = [],
298+
),
299+
]
300+
301+
compilation_db = []
302+
303+
cc_toolchain = find_cpp_toolchain(ctx)
304+
feature_configuration = cc_common.configure_features(
305+
ctx = ctx,
306+
cc_toolchain = cc_toolchain,
307+
requested_features = ctx.features,
308+
unsupported_features = ctx.disabled_features,
309+
)
310+
311+
if ctx.rule.kind in _cc_rules:
312+
compile_commands = _cc_compile_commands(ctx, target, feature_configuration, cc_toolchain)
313+
elif ctx.rule.kind in _objc_rules:
314+
compile_commands = _objc_compile_commands(ctx, target, feature_configuration, cc_toolchain)
315+
else:
316+
fail("unsupported rule: " + ctx.rule.kind)
317+
318+
srcs = []
319+
for compile_command in compile_commands:
320+
exec_root_marker = "__EXEC_ROOT__"
321+
compilation_db.append(
322+
struct(command = compile_command.cmdline, directory = exec_root_marker, file = compile_command.src.path),
323+
)
324+
srcs.append(compile_command.src)
325+
326+
# Write the commands for this target.
327+
compdb_file = ctx.actions.declare_file(ctx.label.name + ".compile_commands.json")
328+
ctx.actions.write(
329+
content = json.encode(compilation_db),
330+
output = compdb_file,
331+
)
332+
333+
compilation_db = depset(compilation_db, transitive = transitive_compilation_db)
334+
all_compdb_files = depset([compdb_file], transitive = all_compdb_files)
335+
all_header_files.append(target[CcInfo].compilation_context.headers)
336+
337+
return [
338+
CompilationAspect(compilation_db = compilation_db),
339+
OutputGroupInfo(
340+
compdb_files = all_compdb_files,
341+
header_files = depset(transitive = all_header_files),
342+
# Provide direct src files of this target for people who want to
343+
# run clang-tidy or similar tools with the compilation database
344+
# on the source files of this target.
345+
# See https://github.com/grailbio/bazel-compilation-database/pull/53.
346+
direct_src_files = srcs,
347+
),
348+
]
349+
350+
compilation_database_aspect = aspect(
351+
# Also include srcs in the attribute aspects so people can use filegroup targets.
352+
# See https://github.com/grailbio/bazel-compilation-database/issues/84.
353+
attr_aspects = ["srcs", "deps"],
354+
attrs = {
355+
"_cc_toolchain": attr.label(
356+
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
357+
),
358+
"_xcode_config": attr.label(default = Label("@bazel_tools//tools/osx:current_xcode_config")),
359+
},
360+
fragments = ["cpp", "objc", "apple"],
361+
provides = [CompilationAspect],
362+
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
363+
implementation = _compilation_database_aspect_impl,
364+
apply_to_generating_rules = True,
365+
)

0 commit comments

Comments
 (0)