Skip to content

Commit 9b9465d

Browse files
committed
Add Clang -ftime-trace support to rules_cc
Declare trace JSON files as compile action outputs and expose them via the trace_files output group, enabled with features = ["trace"] on Clang toolchains.
1 parent 69561f1 commit 9b9465d

7 files changed

Lines changed: 186 additions & 2 deletions

File tree

cc/common/cc_helper.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ def _build_output_groups_for_emitting_compile_providers(
208208
else:
209209
output_groups_builder["module_files"] = depset(compilation_outputs._module_files)
210210

211+
# Add trace JSON files to output groups
212+
if hasattr(compilation_outputs, "_trace_files") and compilation_outputs._trace_files:
213+
output_groups_builder["trace_files"] = depset(compilation_outputs._trace_files)
214+
if hasattr(compilation_outputs, "_pic_trace_files") and compilation_outputs._pic_trace_files:
215+
pic_trace_group = output_groups_builder.get("trace_files", depset())
216+
output_groups_builder["trace_files"] = depset(transitive = [pic_trace_group, depset(compilation_outputs._pic_trace_files)])
217+
211218
if generate_hidden_top_level_group:
212219
output_groups_builder["_hidden_top_level_INTERNAL_"] = _collect_library_hidden_top_level_artifacts(
213220
ctx,

cc/private/compile/cc_compilation_outputs.bzl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ CcCompilationOutputsInfo = provider(
3333
"_pic_dwo_files": "(list[File]) All .pic.dwo files built by the target, corresponding to .pic.o outputs.",
3434
"_gcno_files": "(list[File]) All .gcno files built by the target, corresponding to .o outputs.",
3535
"_pic_gcno_files": "(list[File]) All .pic.gcno files built by the target, corresponding to .pic.gcno outputs.",
36+
"_trace_files": "(list[File]) All .json files built by the target, corresponding to .o outputs.",
37+
"_pic_trace_files": "(list[File]) All .pic.json files built by the target, corresponding to .pic.o outputs.",
3638
"temps": '(() -> depset[File]) All artifacts that are created if "--save_temps" is true.',
3739
"_header_tokens": "(list[File]) All token .h.processed files created when preprocessing or parsing headers.",
3840
"_module_files": "(list[File]) All .pcm files built by the target.",
@@ -52,6 +54,8 @@ def create_compilation_outputs_internal(
5254
pic_dwo_files = [],
5355
gcno_files = [],
5456
pic_gcno_files = [],
57+
trace_files = [],
58+
pic_trace_files = [],
5559
temps = [],
5660
header_tokens = [],
5761
module_files = [],
@@ -69,6 +73,8 @@ def create_compilation_outputs_internal(
6973
pic_dwo_files: A list of PIC dwo files.
7074
gcno_files: A list of gcno files.
7175
pic_gcno_files: A list of PIC gcno files.
76+
trace_files: A list of trace JSON files.
77+
pic_trace_files: A list of PIC trace JSON files.
7278
temps: A depset of temporary files.
7379
header_tokens: A list of header tokens.
7480
module_files: A list of module files.
@@ -91,6 +97,8 @@ def create_compilation_outputs_internal(
9197
_lto_compilation_context = lto_compilation_context,
9298
_gcno_files = _cc_internal.freeze(gcno_files),
9399
_pic_gcno_files = _cc_internal.freeze(pic_gcno_files),
100+
_trace_files = _cc_internal.freeze(trace_files),
101+
_pic_trace_files = _cc_internal.freeze(pic_trace_files),
94102
_dwo_files = _cc_internal.freeze(dwo_files),
95103
_pic_dwo_files = _cc_internal.freeze(pic_dwo_files),
96104
cpp_module_files = _cc_internal.freeze(cpp_module_files),
@@ -194,6 +202,8 @@ def merge_compilation_outputs(*, compilation_outputs):
194202
pic_dwo_files = []
195203
gcno_files = []
196204
pic_gcno_files = []
205+
trace_files = []
206+
pic_trace_files = []
197207
lto_compilation_contexts = []
198208
transitive_temps = []
199209
header_tokens = []
@@ -206,6 +216,8 @@ def merge_compilation_outputs(*, compilation_outputs):
206216
pic_dwo_files.extend(co._pic_dwo_files)
207217
gcno_files.extend(co._gcno_files)
208218
pic_gcno_files.extend(co._pic_gcno_files)
219+
trace_files.extend(co._trace_files)
220+
pic_trace_files.extend(co._pic_trace_files)
209221
transitive_temps.append(co.temps())
210222
header_tokens.extend(co._header_tokens)
211223
module_files.extend(co._module_files)
@@ -220,6 +232,8 @@ def merge_compilation_outputs(*, compilation_outputs):
220232
_pic_dwo_files = pic_dwo_files,
221233
_gcno_files = gcno_files,
222234
_pic_gcno_files = pic_gcno_files,
235+
_trace_files = trace_files,
236+
_pic_trace_files = pic_trace_files,
223237
temps = wrap_with_check_private_api(depset(transitive = transitive_temps)),
224238
_header_tokens = header_tokens,
225239
_module_files = module_files,

cc/private/compile/compile.bzl

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ load("//cc/private/compile:lto_compilation_context.bzl", "create_lto_compilation
5252

5353
_VALID_CPP_SOURCE_TYPES = set([CPP_SOURCE_TYPE_SOURCE, CPP_SOURCE_TYPE_HEADER, CPP_SOURCE_TYPE_CLIF_INPUT_PROTO])
5454

55+
def _clang_trace_enabled(feature_configuration):
56+
return feature_configuration.is_enabled("clang_trace")
57+
5558
def _cpp_source_init(*, label, source, type):
5659
if type not in _VALID_CPP_SOURCE_TYPES:
5760
fail("invalid type of cpp source, got:", type, "expected one of:", _VALID_CPP_SOURCE_TYPES)
@@ -347,6 +350,8 @@ def compile(
347350
"lto_compilation_context": {},
348351
"gcno_files": [],
349352
"pic_gcno_files": [],
353+
"trace_files": [],
354+
"pic_trace_files": [],
350355
"dwo_files": [],
351356
"pic_dwo_files": [],
352357
"cpp_module_files": [],
@@ -837,6 +842,7 @@ def _create_cc_compile_actions_with_cpp20_module_helper(
837842
cpp_module_map = cc_compilation_context._module_map,
838843
add_object = True,
839844
enable_coverage = is_code_coverage_enabled,
845+
enable_trace = _clang_trace_enabled(feature_configuration),
840846
generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration),
841847
bitcode_output = bitcode_output,
842848
fdo_context = fdo_context,
@@ -985,6 +991,7 @@ def _create_cc_compile_actions_with_cpp20_module_helper(
985991
cpp_module_map = cc_compilation_context._module_map,
986992
add_object = True,
987993
enable_coverage = is_code_coverage_enabled,
994+
enable_trace = _clang_trace_enabled(feature_configuration),
988995
generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration),
989996
bitcode_output = bitcode_output,
990997
fdo_context = fdo_context,
@@ -1268,6 +1275,7 @@ def _create_cc_compile_actions(
12681275
cpp_module_map = cc_compilation_context._module_map,
12691276
add_object = True,
12701277
enable_coverage = is_code_coverage_enabled,
1278+
enable_trace = _clang_trace_enabled(feature_configuration),
12711279
generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration),
12721280
bitcode_output = bitcode_output,
12731281
fdo_context = fdo_context,
@@ -1420,6 +1428,7 @@ def _create_pic_nopic_compile_source_actions(
14201428
cpp_module_map,
14211429
add_object,
14221430
enable_coverage,
1431+
enable_trace,
14231432
generate_dwo,
14241433
bitcode_output,
14251434
fdo_context,
@@ -1454,6 +1463,7 @@ def _create_pic_nopic_compile_source_actions(
14541463
cpp_module_map = cpp_module_map,
14551464
add_object = add_object,
14561465
enable_coverage = enable_coverage,
1466+
enable_trace = enable_trace,
14571467
generate_dwo = generate_dwo,
14581468
bitcode_output = bitcode_output,
14591469
fdo_context = fdo_context,
@@ -1491,6 +1501,7 @@ def _create_pic_nopic_compile_source_actions(
14911501
cpp_module_map = cpp_module_map,
14921502
add_object = add_object,
14931503
enable_coverage = enable_coverage,
1504+
enable_trace = enable_trace,
14941505
generate_dwo = generate_dwo,
14951506
bitcode_output = bitcode_output,
14961507
fdo_context = fdo_context,
@@ -1529,6 +1540,7 @@ def _create_compile_source_action(
15291540
cpp_module_map,
15301541
add_object,
15311542
enable_coverage,
1543+
enable_trace,
15321544
generate_dwo,
15331545
bitcode_output,
15341546
fdo_context,
@@ -1592,6 +1604,11 @@ def _create_compile_source_action(
15921604
configuration = configuration,
15931605
enable_coverage = enable_coverage,
15941606
)
1607+
trace_file = _maybe_declare_trace_file(
1608+
ctx = action_construction_context,
1609+
enable_trace = enable_trace,
1610+
object_file = object_file,
1611+
)
15951612

15961613
dwo_file = None
15971614
if generate_dwo and not bitcode_output:
@@ -1680,14 +1697,20 @@ def _create_compile_source_action(
16801697
if add_object and fdo_context_has_artifacts:
16811698
additional_inputs = additional_compilation_inputs + auxiliary_fdo_inputs.to_list()
16821699

1700+
all_additional_outputs = list(additional_outputs)
1701+
if trace_file:
1702+
all_additional_outputs.append(trace_file)
1703+
16831704
# Provide these args conditionally as they require a recent version of Bazel.
16841705
if modmap_file:
16851706
module_args = {
1686-
"additional_outputs": additional_outputs,
1707+
"additional_outputs": all_additional_outputs,
16871708
"module_files": module_files,
16881709
"modmap_file": modmap_file,
16891710
"modmap_input_file": modmap_input_file,
16901711
}
1712+
elif all_additional_outputs:
1713+
module_args = {"additional_outputs": all_additional_outputs}
16911714
else:
16921715
module_args = {}
16931716

@@ -1736,6 +1759,11 @@ def _create_compile_source_action(
17361759
outputs["pic_gcno_files"].append(gcno_file)
17371760
else:
17381761
outputs["gcno_files"].append(gcno_file)
1762+
if trace_file:
1763+
if use_pic:
1764+
outputs["pic_trace_files"].append(trace_file)
1765+
else:
1766+
outputs["trace_files"].append(trace_file)
17391767
return object_file
17401768

17411769
def _create_temps_action(
@@ -2136,6 +2164,7 @@ def _create_module_action(
21362164
cpp_module_map = cpp_module_map,
21372165
add_object = False,
21382166
enable_coverage = False,
2167+
enable_trace = False,
21392168
generate_dwo = False,
21402169
bitcode_output = False,
21412170
additional_compilation_inputs = additional_compilation_inputs,
@@ -2297,12 +2326,25 @@ def _maybe_declare_gcno_file(
22972326
)
22982327
return gcno_file
22992328

2329+
def _maybe_declare_trace_file(
2330+
ctx,
2331+
enable_trace,
2332+
object_file):
2333+
if not enable_trace:
2334+
return None
2335+
return _cc_internal.declare_other_output_file(
2336+
ctx = ctx,
2337+
output_name = paths.replace_extension(paths.basename(object_file.path), ".json"),
2338+
object_file = object_file,
2339+
)
2340+
23002341
def _create_compile_action(
23012342
*,
23022343
action_construction_context = None,
23032344
action_name = None,
23042345
additional_compilation_inputs = None,
23052346
additional_include_scanning_roots = [],
2347+
additional_outputs = [],
23062348
cc_compilation_context = None,
23072349
cc_toolchain = None,
23082350
compile_build_variables = None,
@@ -2329,6 +2371,7 @@ def _create_compile_action(
23292371
action_name = action_name,
23302372
additional_compilation_inputs = additional_compilation_inputs,
23312373
additional_include_scanning_roots = additional_include_scanning_roots,
2374+
additional_outputs = additional_outputs,
23322375
cc_compilation_context = cc_compilation_context,
23332376
cc_toolchain = cc_toolchain,
23342377
compile_build_variables = compile_build_variables,

cc/private/compile/compile_build_variables.bzl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,6 @@ def get_specific_compile_build_variables(
343343
# TODO: Blaze currently uses `gcov_gcno_file` to detect if code coverage
344344
# is enabled. It should use a different signal.
345345
result[_VARS.GCOV_GCNO_FILE] = ""
346-
347346
if dwo_file:
348347
result[_VARS.PER_OBJECT_DEBUG_INFO_FILE] = dwo_file
349348
if using_fission:

cc/private/toolchain/unix_cc_toolchain_config.bzl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,6 +1869,35 @@ def _impl(ctx):
18691869

18701870
skip_virtual_includes_feature = feature(name = "skip_virtual_includes")
18711871

1872+
# Clang -ftime-trace support
1873+
trace_feature = feature(
1874+
name = "trace",
1875+
provides = ["trace"],
1876+
)
1877+
1878+
clang_trace_feature = feature(
1879+
name = "clang_trace",
1880+
flag_sets = [
1881+
flag_set(
1882+
actions = [
1883+
ACTION_NAMES.preprocess_assemble,
1884+
ACTION_NAMES.c_compile,
1885+
ACTION_NAMES.cpp_compile,
1886+
ACTION_NAMES.cpp_module_compile,
1887+
ACTION_NAMES.objc_compile,
1888+
ACTION_NAMES.objcpp_compile,
1889+
],
1890+
flag_groups = [
1891+
flag_group(
1892+
flags = ["-ftime-trace"],
1893+
),
1894+
],
1895+
),
1896+
],
1897+
requires = [feature_set(features = ["trace"])],
1898+
enabled = True,
1899+
)
1900+
18721901
# TODO(#8303): Mac crosstool should also declare every feature.
18731902
if is_linux:
18741903
# Linux artifact name patterns are the default.
@@ -2016,6 +2045,11 @@ def _impl(ctx):
20162045
extra_rules_based_features = depset(ctx.attr.extra_enabled_features + ctx.attr.extra_known_features)
20172046
features.extend([convert_feature(extra_feature[FeatureInfo], enabled = extra_feature in ctx.attr.extra_enabled_features) for extra_feature in extra_rules_based_features.to_list()])
20182047

2048+
features.extend([
2049+
trace_feature,
2050+
])
2051+
if ctx.attr.compiler == "clang":
2052+
features.append(clang_trace_feature)
20192053
return cc_common.create_cc_toolchain_config_info(
20202054
ctx = ctx,
20212055
features = features,

tests/integration/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,14 @@ rules_cc_integration_test(
4343
target_compatible_with = VERSION_GATE,
4444
test_script = "cc_static_library_failure_tests.sh",
4545
)
46+
47+
rules_cc_integration_test(
48+
name = "ftime_trace_tests",
49+
timeout = "long",
50+
target_compatible_with = select({
51+
"@platforms//os:macos": ["@platforms//:incompatible"],
52+
"@platforms//os:windows": ["@platforms//:incompatible"],
53+
"//conditions:default": [],
54+
}),
55+
test_script = "ftime_trace_tests.sh",
56+
)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
set -euo pipefail
2+
3+
source "$(rlocation rules_cc/tests/test_utils.sh)"
4+
source "$(rlocation rules_cc/tests/unittest.bash)"
5+
6+
function set_up() {
7+
if is_windows; then
8+
echo "Skipping on Windows."
9+
return 1
10+
fi
11+
12+
local -r clang="$(which clang || true)"
13+
local -r clangxx="$(which clang++ || true)"
14+
if [[ ! -x "$clang" || ! -x "$clangxx" ]]; then
15+
echo "clang/clang++ not installed. Skipping test."
16+
return 1
17+
fi
18+
19+
add_to_bazelrc "common --repo_env=CC=$clang"
20+
add_to_bazelrc "common --repo_env=CXX=$clangxx"
21+
}
22+
23+
function test_ftime_trace_outputs() {
24+
# Bazel versions before 9 use the built-in native C++ rules, not rules_cc.
25+
# This integration test exercises the rules_cc -ftime-trace implementation only.
26+
local bazel_version=""
27+
bazel_version="$(bazel version 2>/dev/null | awk '/^(Build label|Release label):/ { print $3; exit }')"
28+
local bazel_major="${bazel_version%%.*}"
29+
if [[ -z "$bazel_major" || "$bazel_major" -lt 9 ]]; then
30+
echo "Bazel ${bazel_version:-unknown} is older than 9.0; native C++ rules are used instead of rules_cc. Skipping test."
31+
return 0
32+
fi
33+
34+
cat > BUILD << EOF
35+
load("@rules_cc//cc:cc_library.bzl", "cc_library")
36+
37+
cc_library(
38+
name = "lib_with_trace",
39+
srcs = ["foo.cc"],
40+
features = ["trace"],
41+
)
42+
EOF
43+
44+
cat > foo.cc << EOF
45+
int foo() {
46+
return 42;
47+
}
48+
EOF
49+
50+
bazel build //:lib_with_trace --output_groups=trace_files >& "$TEST_log" || fail "Build failed"
51+
52+
trace_json="$(find bazel-bin/_objs/lib_with_trace -name '*.json' | head -1)"
53+
if [[ -z "$trace_json" ]]; then
54+
fail "Expected a Clang -ftime-trace JSON file under bazel-bin/_objs/lib_with_trace"
55+
fi
56+
57+
if [[ ! -s "$trace_json" ]]; then
58+
fail "Trace file is empty: $trace_json"
59+
fi
60+
61+
python3 - "$trace_json" << 'PY'
62+
import json
63+
import sys
64+
65+
path = sys.argv[1]
66+
with open(path, encoding="utf-8") as input_file:
67+
data = json.load(input_file)
68+
69+
if "traceEvents" not in data:
70+
raise SystemExit(f"missing traceEvents key in {path}")
71+
if not isinstance(data["traceEvents"], list) or not data["traceEvents"]:
72+
raise SystemExit(f"traceEvents is empty in {path}")
73+
PY
74+
}
75+
76+
run_suite "Integration tests for Clang -ftime-trace with rules_cc"

0 commit comments

Comments
 (0)