Skip to content

Commit f261be6

Browse files
committed
refactor(cpp/libclang): extract reusable parser action
1 parent 8d77504 commit f261be6

5 files changed

Lines changed: 219 additions & 109 deletions

File tree

cpp/libclang/BUILD

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ rust_binary(
3232
data = [
3333
"@llvm_toolchain_llvm//:libclang",
3434
],
35-
env = {
36-
"LIBCLANG_PATH": "$(rootpath @llvm_toolchain_llvm//:libclang)",
37-
},
3835
visibility = ["//visibility:public"],
3936
deps = [
4037
"//cpp/libclang/src/utils",

cpp/libclang/README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ cpp_parser(
2626
extra_args = [
2727
],
2828
target = "//cpp/libclang/integration_test/cases/include_3rdparty",
29-
tool = ":clang_rs_parser",
3029
)
3130
```
3231

@@ -38,9 +37,9 @@ Where:
3837
Expected result:
3938

4039
- Bazel creates parser output artifact:
41-
- `bazel-bin/cpp/libclang/cpp_parser_include_3rdparty_result.fbs.bin`
40+
- `bazel-bin/cpp/libclang/cpp_parser_include_3rdparty_class_diagram.fbs.bin`
4241
- When `emit_debug_json = True`, the parser also writes:
43-
- `bazel-bin/cpp/libclang/cpp_parser_include_3rdparty_result/debug.json`
42+
- `bazel-bin/cpp/libclang/cpp_parser_include_3rdparty_debug.json`
4443

4544
## Configure debug logging
4645

@@ -55,6 +54,6 @@ Accepted values are: `error`, `warn`, `info`, `debug`, `trace`.
5554
## Quick check (optional)
5655

5756
```bash
58-
ls -l bazel-bin/cpp/libclang/cpp_parser_include_3rdparty_result.fbs.bin
59-
ls -l bazel-bin/cpp/libclang/cpp_parser_include_3rdparty_result/debug.json
57+
ls -l bazel-bin/cpp/libclang/integration_test/cases/include_3rdparty/parser_class_diagram.fbs.bin
58+
ls -l bazel-bin/cpp/libclang/integration_test/cases/include_3rdparty/parser_debug.json
6059
```

cpp/libclang/cpp_parser.bzl

Lines changed: 172 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
1414
load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain", "use_cc_toolchain")
1515
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
16+
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
17+
18+
# Providers and aspects used to adapt C/C++ targets for the parser action.
1619

1720
def _extract_files_from_attr(attr, attr_name):
1821
files = []
@@ -21,7 +24,13 @@ def _extract_files_from_attr(attr, attr_name):
2124
files.extend(src.files.to_list())
2225
return files
2326

24-
SourceFilesInfo = provider(fields = ["files", "inputs"])
27+
SourceFilesInfo = provider(
28+
doc = "Source files and transitive inputs collected for parser actions.",
29+
fields = {
30+
"files": "depset of source files to parse for this target.",
31+
"inputs": "depset of source and header inputs needed by the parser action.",
32+
},
33+
)
2534

2635
def _cc_sources_aspect_impl(target, ctx):
2736
direct_srcs = _extract_files_from_attr(ctx.rule.attr, "srcs")
@@ -64,7 +73,15 @@ CompilationFlagsInfo = provider(
6473
},
6574
)
6675

67-
def _collect_from_cc_info(target):
76+
CppParserInfo = provider(
77+
doc = "Outputs generated by the C/C++ parser.",
78+
fields = {
79+
"class_fbs": "Class diagram FlatBuffer output file.",
80+
"debug_json": "Debug JSON sidecar output file. (Optional)",
81+
},
82+
)
83+
84+
def _collect_from_cc_info(target, ctx):
6885
flags = []
6986

7087
if CcInfo in target:
@@ -90,7 +107,7 @@ def _compilation_flags_aspect_impl(target, ctx):
90107
if CompilationFlagsInfo in dep:
91108
transitive.append(dep[CompilationFlagsInfo].flags)
92109

93-
direct_flags = _collect_from_cc_info(target)
110+
direct_flags = _collect_from_cc_info(target, ctx)
94111

95112
return [
96113
CompilationFlagsInfo(
@@ -103,6 +120,53 @@ compilation_flags_aspect = aspect(
103120
attr_aspects = ["deps"],
104121
)
105122

123+
# Public integration helpers for rules that want to reuse the parser action.
124+
125+
def cpp_parser_target_aspects():
126+
"""Return aspects required on attrs whose targets will be parsed."""
127+
128+
return [cc_sources_aspect, compilation_flags_aspect]
129+
130+
def has_cpp_parser_inputs(target):
131+
"""Check whether a target has the providers needed by run_cpp_parser_action."""
132+
133+
return (CcInfo in target and
134+
SourceFilesInfo in target and
135+
CompilationFlagsInfo in target and
136+
bool(target[SourceFilesInfo].files.to_list()))
137+
138+
def cpp_parser_action_internal_attrs():
139+
"""Return private attrs required by run_cpp_parser_action callers."""
140+
141+
return {
142+
"_tool": attr.label(
143+
default = Label("//cpp/libclang:clang_rs_parser"),
144+
executable = True,
145+
cfg = "exec",
146+
doc = "Internal libclang-based parser backend.",
147+
),
148+
"_libclang": attr.label(
149+
allow_single_file = True,
150+
default = "@llvm_toolchain_llvm//:lib/libclang.so",
151+
),
152+
"_llvm_cxx_builtin_include": attr.label(
153+
default = "@llvm_toolchain_llvm//:cxx_builtin_include",
154+
doc = "LLVM toolchain filegroup containing the libc++ header directory (include/c++) " +
155+
"and the clang resource include directory (lib/clang/<version>/include).",
156+
),
157+
"_llvm_extra_config_site": attr.label(
158+
default = "@llvm_toolchain_llvm//:extra_config_site",
159+
doc = "LLVM toolchain filegroup containing the arch-specific __config_site file " +
160+
"(include/<triple>/c++/v1/__config_site) used to locate the ABI include path.",
161+
),
162+
"_log_level": attr.label(
163+
default = Label("//cpp/libclang:log_level"),
164+
doc = "Build setting that controls clang_rs_parser log level.",
165+
),
166+
}
167+
168+
# Parser action implementation.
169+
106170
def _detect_standard_from_flags(ctx):
107171
"""
108172
Fall back: compile the action's compile flags and look for -std=.
@@ -190,58 +254,87 @@ def _collect_required_llvm_include_args(cxx_builtin_include_files, extra_config_
190254

191255
return result
192256

193-
def _cpp_parser_impl(ctx):
194-
output_dir = ctx.actions.declare_directory(
195-
ctx.label.name + "_result",
257+
def run_cpp_parser_action(
258+
ctx,
259+
*,
260+
target,
261+
output_prefix,
262+
tool,
263+
libclang,
264+
llvm_cxx_builtin_include,
265+
llvm_extra_config_site,
266+
log_level,
267+
extra_args = [],
268+
emit_debug_json = False):
269+
"""Register the libclang parser action and return its declared outputs.
270+
271+
The target must be analyzed with cpp_parser_target_aspects() before this
272+
helper is called. Use has_cpp_parser_inputs() when the caller accepts mixed
273+
implementation target types.
274+
"""
275+
276+
class_fbs_output = ctx.actions.declare_file(
277+
"{}_{}".format(output_prefix, "class_diagram.fbs.bin"),
196278
)
197-
libclang = ctx.file._libclang
198279

199-
args = []
280+
debug_json_output = None
281+
if emit_debug_json:
282+
debug_json_output = ctx.actions.declare_file(
283+
"{}_{}".format(output_prefix, "debug.json"),
284+
)
285+
286+
tool_files_to_run = tool[DefaultInfo].files_to_run
200287

288+
args = []
201289
args += [
202-
"--output-dir",
203-
output_dir.path,
290+
"--class-fbs-output",
291+
class_fbs_output.path,
204292
]
205293

206-
if ctx.attr.emit_debug_json:
207-
args.append("--json")
294+
if debug_json_output:
295+
args += [
296+
"--debug-json-output",
297+
debug_json_output.path,
298+
]
208299

209-
target_compilation_flags_list = ctx.attr.target[CompilationFlagsInfo].flags.to_list()
300+
target_compilation_flags_list = target[CompilationFlagsInfo].flags.to_list()
210301

211-
cxx_builtin_include_files = ctx.attr._llvm_cxx_builtin_include.files.to_list()
212-
extra_config_site_files = ctx.attr._llvm_extra_config_site.files.to_list()
302+
cxx_builtin_include_files = llvm_cxx_builtin_include.files.to_list()
303+
extra_config_site_files = llvm_extra_config_site.files.to_list()
213304
llvm_include_args = _collect_required_llvm_include_args(cxx_builtin_include_files, extra_config_site_files)
214305

215-
extra_args = [
306+
parser_extra_args = [
216307
_detect_standard_from_flags(ctx),
217308
"-nostdinc++",
218309
]
219-
extra_args += llvm_include_args
220-
extra_args += target_compilation_flags_list + ctx.attr.extra_args
221-
for ea in extra_args:
310+
parser_extra_args += llvm_include_args
311+
parser_extra_args += target_compilation_flags_list + extra_args
312+
for ea in parser_extra_args:
222313
args += ["--extra-arg", ea]
223314

224-
target_source_files_info = ctx.attr.target[SourceFilesInfo]
315+
target_source_files_info = target[SourceFilesInfo]
225316
target_source_files_list = target_source_files_info.files.to_list()
226317
target_source_inputs_list = target_source_files_info.inputs.to_list()
227318

228-
# extend args with input sources
229-
if SourceFilesInfo in ctx.attr.target:
230-
args += [file.path for file in target_source_files_list]
319+
args += ["--input"] + [file.path for file in target_source_files_list]
231320

232321
inputs = [
233322
libclang,
234323
] + target_source_inputs_list + cxx_builtin_include_files + extra_config_site_files
235324

325+
outputs = [class_fbs_output]
326+
if debug_json_output:
327+
outputs.append(debug_json_output)
328+
236329
ctx.actions.run(
237330
inputs = inputs,
238-
outputs = [output_dir],
239-
executable = ctx.executable.tool,
240-
tools = [ctx.attr.tool[DefaultInfo].files_to_run],
331+
outputs = outputs,
332+
executable = tool_files_to_run.executable,
333+
tools = [tool_files_to_run],
241334
arguments = args,
242335
env = {
243336
"LIBCLANG_PATH": libclang.dirname,
244-
"LIBCLANG_LOG": ctx.attr._log_level[BuildSettingInfo].value,
337+
"LIBCLANG_LOG": log_level,
245338
},
246339
mnemonic = "CppAnalyze",
247340
# this is required to parse some system headers
@@ -251,49 +344,62 @@ def _cpp_parser_impl(ctx):
251344
progress_message = "Running C++ AST analysis: %s" % ctx.label,
252345
)
253346

254-
return DefaultInfo(
255-
files = depset([output_dir]),
256-
runfiles = ctx.runfiles(files = [output_dir]),
347+
return struct(
348+
class_fbs = class_fbs_output,
349+
debug_json = debug_json_output,
257350
)
258351

259-
cpp_parser = rule(
260-
implementation = _cpp_parser_impl,
261-
attrs = {
262-
"target": attr.label(
263-
aspects = [cc_sources_aspect, compilation_flags_aspect],
264-
mandatory = True,
265-
),
266-
"tool": attr.label(
267-
executable = True,
268-
cfg = "exec",
269-
mandatory = True,
270-
),
271-
"extra_args": attr.string_list(
272-
default = [],
273-
),
274-
"emit_debug_json": attr.bool(
275-
default = False,
276-
doc = "Emit debug.json alongside the FlatBuffer output. Intended for tests/debugging.",
277-
),
278-
"_libclang": attr.label(
279-
allow_single_file = True,
280-
default = "@llvm_toolchain_llvm//:lib/libclang.so",
281-
),
282-
"_llvm_cxx_builtin_include": attr.label(
283-
default = "@llvm_toolchain_llvm//:cxx_builtin_include",
284-
doc = "LLVM toolchain filegroup containing the libc++ header directory (include/c++) " +
285-
"and the clang resource include directory (lib/clang/<version>/include).",
286-
),
287-
"_llvm_extra_config_site": attr.label(
288-
default = "@llvm_toolchain_llvm//:extra_config_site",
289-
doc = "LLVM toolchain filegroup containing the arch-specific __config_site file " +
290-
"(include/<triple>/c++/v1/__config_site) used to locate the ABI include path.",
352+
# cpp_parser rule
353+
354+
def _cpp_parser_impl(ctx):
355+
outputs = run_cpp_parser_action(
356+
ctx,
357+
target = ctx.attr.target,
358+
output_prefix = ctx.label.name,
359+
tool = ctx.attr._tool,
360+
libclang = ctx.file._libclang,
361+
llvm_cxx_builtin_include = ctx.attr._llvm_cxx_builtin_include,
362+
llvm_extra_config_site = ctx.attr._llvm_extra_config_site,
363+
log_level = ctx.attr._log_level[BuildSettingInfo].value,
364+
extra_args = ctx.attr.extra_args,
365+
emit_debug_json = ctx.attr.emit_debug_json,
366+
)
367+
368+
runfiles_files = [outputs.class_fbs]
369+
if outputs.debug_json:
370+
runfiles_files.append(outputs.debug_json)
371+
372+
return [
373+
DefaultInfo(
374+
files = depset([outputs.class_fbs]),
375+
runfiles = ctx.runfiles(files = runfiles_files),
291376
),
292-
"_log_level": attr.label(
293-
default = Label("//cpp/libclang:log_level"),
294-
doc = "Build setting that controls clang_rs_parser log level.",
377+
CppParserInfo(
378+
class_fbs = outputs.class_fbs,
379+
debug_json = outputs.debug_json,
295380
),
296-
},
381+
]
382+
383+
_cpp_parser_attrs = {
384+
"target": attr.label(
385+
aspects = cpp_parser_target_aspects(),
386+
mandatory = True,
387+
),
388+
"extra_args": attr.string_list(
389+
default = [],
390+
),
391+
"emit_debug_json": attr.bool(
392+
default = False,
393+
doc = "Emit debug.json alongside the FlatBuffer output. Intended for tests/debugging.",
394+
),
395+
}
396+
397+
_cpp_parser_attrs.update(cpp_parser_action_internal_attrs())
398+
399+
# BUILD-facing parser rule: wraps run_cpp_parser_action and exposes CppParserInfo.
400+
cpp_parser = rule(
401+
implementation = _cpp_parser_impl,
402+
attrs = _cpp_parser_attrs,
297403
toolchains = use_cc_toolchain(),
298404
fragments = ["cpp"],
299405
)

0 commit comments

Comments
 (0)