From 9b9465d5e02ec9064143020fc42bd778fe10138c Mon Sep 17 00:00:00 2001 From: PikachuHy Date: Fri, 26 Jun 2026 14:16:18 +0800 Subject: [PATCH] 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. --- cc/common/cc_helper.bzl | 7 ++ cc/private/compile/cc_compilation_outputs.bzl | 14 ++++ cc/private/compile/compile.bzl | 45 ++++++++++- .../compile/compile_build_variables.bzl | 1 - .../toolchain/unix_cc_toolchain_config.bzl | 34 +++++++++ tests/integration/BUILD | 11 +++ tests/integration/ftime_trace_tests.sh | 76 +++++++++++++++++++ 7 files changed, 186 insertions(+), 2 deletions(-) create mode 100755 tests/integration/ftime_trace_tests.sh diff --git a/cc/common/cc_helper.bzl b/cc/common/cc_helper.bzl index 92483ac6d..56099c45b 100644 --- a/cc/common/cc_helper.bzl +++ b/cc/common/cc_helper.bzl @@ -208,6 +208,13 @@ def _build_output_groups_for_emitting_compile_providers( else: output_groups_builder["module_files"] = depset(compilation_outputs._module_files) + # Add trace JSON files to output groups + if hasattr(compilation_outputs, "_trace_files") and compilation_outputs._trace_files: + output_groups_builder["trace_files"] = depset(compilation_outputs._trace_files) + if hasattr(compilation_outputs, "_pic_trace_files") and compilation_outputs._pic_trace_files: + pic_trace_group = output_groups_builder.get("trace_files", depset()) + output_groups_builder["trace_files"] = depset(transitive = [pic_trace_group, depset(compilation_outputs._pic_trace_files)]) + if generate_hidden_top_level_group: output_groups_builder["_hidden_top_level_INTERNAL_"] = _collect_library_hidden_top_level_artifacts( ctx, diff --git a/cc/private/compile/cc_compilation_outputs.bzl b/cc/private/compile/cc_compilation_outputs.bzl index 95f500c61..e1a59cd82 100644 --- a/cc/private/compile/cc_compilation_outputs.bzl +++ b/cc/private/compile/cc_compilation_outputs.bzl @@ -33,6 +33,8 @@ CcCompilationOutputsInfo = provider( "_pic_dwo_files": "(list[File]) All .pic.dwo files built by the target, corresponding to .pic.o outputs.", "_gcno_files": "(list[File]) All .gcno files built by the target, corresponding to .o outputs.", "_pic_gcno_files": "(list[File]) All .pic.gcno files built by the target, corresponding to .pic.gcno outputs.", + "_trace_files": "(list[File]) All .json files built by the target, corresponding to .o outputs.", + "_pic_trace_files": "(list[File]) All .pic.json files built by the target, corresponding to .pic.o outputs.", "temps": '(() -> depset[File]) All artifacts that are created if "--save_temps" is true.', "_header_tokens": "(list[File]) All token .h.processed files created when preprocessing or parsing headers.", "_module_files": "(list[File]) All .pcm files built by the target.", @@ -52,6 +54,8 @@ def create_compilation_outputs_internal( pic_dwo_files = [], gcno_files = [], pic_gcno_files = [], + trace_files = [], + pic_trace_files = [], temps = [], header_tokens = [], module_files = [], @@ -69,6 +73,8 @@ def create_compilation_outputs_internal( pic_dwo_files: A list of PIC dwo files. gcno_files: A list of gcno files. pic_gcno_files: A list of PIC gcno files. + trace_files: A list of trace JSON files. + pic_trace_files: A list of PIC trace JSON files. temps: A depset of temporary files. header_tokens: A list of header tokens. module_files: A list of module files. @@ -91,6 +97,8 @@ def create_compilation_outputs_internal( _lto_compilation_context = lto_compilation_context, _gcno_files = _cc_internal.freeze(gcno_files), _pic_gcno_files = _cc_internal.freeze(pic_gcno_files), + _trace_files = _cc_internal.freeze(trace_files), + _pic_trace_files = _cc_internal.freeze(pic_trace_files), _dwo_files = _cc_internal.freeze(dwo_files), _pic_dwo_files = _cc_internal.freeze(pic_dwo_files), cpp_module_files = _cc_internal.freeze(cpp_module_files), @@ -194,6 +202,8 @@ def merge_compilation_outputs(*, compilation_outputs): pic_dwo_files = [] gcno_files = [] pic_gcno_files = [] + trace_files = [] + pic_trace_files = [] lto_compilation_contexts = [] transitive_temps = [] header_tokens = [] @@ -206,6 +216,8 @@ def merge_compilation_outputs(*, compilation_outputs): pic_dwo_files.extend(co._pic_dwo_files) gcno_files.extend(co._gcno_files) pic_gcno_files.extend(co._pic_gcno_files) + trace_files.extend(co._trace_files) + pic_trace_files.extend(co._pic_trace_files) transitive_temps.append(co.temps()) header_tokens.extend(co._header_tokens) module_files.extend(co._module_files) @@ -220,6 +232,8 @@ def merge_compilation_outputs(*, compilation_outputs): _pic_dwo_files = pic_dwo_files, _gcno_files = gcno_files, _pic_gcno_files = pic_gcno_files, + _trace_files = trace_files, + _pic_trace_files = pic_trace_files, temps = wrap_with_check_private_api(depset(transitive = transitive_temps)), _header_tokens = header_tokens, _module_files = module_files, diff --git a/cc/private/compile/compile.bzl b/cc/private/compile/compile.bzl index d7272e1d2..400d9cb22 100644 --- a/cc/private/compile/compile.bzl +++ b/cc/private/compile/compile.bzl @@ -52,6 +52,9 @@ load("//cc/private/compile:lto_compilation_context.bzl", "create_lto_compilation _VALID_CPP_SOURCE_TYPES = set([CPP_SOURCE_TYPE_SOURCE, CPP_SOURCE_TYPE_HEADER, CPP_SOURCE_TYPE_CLIF_INPUT_PROTO]) +def _clang_trace_enabled(feature_configuration): + return feature_configuration.is_enabled("clang_trace") + def _cpp_source_init(*, label, source, type): if type not in _VALID_CPP_SOURCE_TYPES: fail("invalid type of cpp source, got:", type, "expected one of:", _VALID_CPP_SOURCE_TYPES) @@ -347,6 +350,8 @@ def compile( "lto_compilation_context": {}, "gcno_files": [], "pic_gcno_files": [], + "trace_files": [], + "pic_trace_files": [], "dwo_files": [], "pic_dwo_files": [], "cpp_module_files": [], @@ -837,6 +842,7 @@ def _create_cc_compile_actions_with_cpp20_module_helper( cpp_module_map = cc_compilation_context._module_map, add_object = True, enable_coverage = is_code_coverage_enabled, + enable_trace = _clang_trace_enabled(feature_configuration), generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -985,6 +991,7 @@ def _create_cc_compile_actions_with_cpp20_module_helper( cpp_module_map = cc_compilation_context._module_map, add_object = True, enable_coverage = is_code_coverage_enabled, + enable_trace = _clang_trace_enabled(feature_configuration), generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1268,6 +1275,7 @@ def _create_cc_compile_actions( cpp_module_map = cc_compilation_context._module_map, add_object = True, enable_coverage = is_code_coverage_enabled, + enable_trace = _clang_trace_enabled(feature_configuration), generate_dwo = should_create_per_object_debug_info(feature_configuration, cpp_configuration), bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1420,6 +1428,7 @@ def _create_pic_nopic_compile_source_actions( cpp_module_map, add_object, enable_coverage, + enable_trace, generate_dwo, bitcode_output, fdo_context, @@ -1454,6 +1463,7 @@ def _create_pic_nopic_compile_source_actions( cpp_module_map = cpp_module_map, add_object = add_object, enable_coverage = enable_coverage, + enable_trace = enable_trace, generate_dwo = generate_dwo, bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1491,6 +1501,7 @@ def _create_pic_nopic_compile_source_actions( cpp_module_map = cpp_module_map, add_object = add_object, enable_coverage = enable_coverage, + enable_trace = enable_trace, generate_dwo = generate_dwo, bitcode_output = bitcode_output, fdo_context = fdo_context, @@ -1529,6 +1540,7 @@ def _create_compile_source_action( cpp_module_map, add_object, enable_coverage, + enable_trace, generate_dwo, bitcode_output, fdo_context, @@ -1592,6 +1604,11 @@ def _create_compile_source_action( configuration = configuration, enable_coverage = enable_coverage, ) + trace_file = _maybe_declare_trace_file( + ctx = action_construction_context, + enable_trace = enable_trace, + object_file = object_file, + ) dwo_file = None if generate_dwo and not bitcode_output: @@ -1680,14 +1697,20 @@ def _create_compile_source_action( if add_object and fdo_context_has_artifacts: additional_inputs = additional_compilation_inputs + auxiliary_fdo_inputs.to_list() + all_additional_outputs = list(additional_outputs) + if trace_file: + all_additional_outputs.append(trace_file) + # Provide these args conditionally as they require a recent version of Bazel. if modmap_file: module_args = { - "additional_outputs": additional_outputs, + "additional_outputs": all_additional_outputs, "module_files": module_files, "modmap_file": modmap_file, "modmap_input_file": modmap_input_file, } + elif all_additional_outputs: + module_args = {"additional_outputs": all_additional_outputs} else: module_args = {} @@ -1736,6 +1759,11 @@ def _create_compile_source_action( outputs["pic_gcno_files"].append(gcno_file) else: outputs["gcno_files"].append(gcno_file) + if trace_file: + if use_pic: + outputs["pic_trace_files"].append(trace_file) + else: + outputs["trace_files"].append(trace_file) return object_file def _create_temps_action( @@ -2136,6 +2164,7 @@ def _create_module_action( cpp_module_map = cpp_module_map, add_object = False, enable_coverage = False, + enable_trace = False, generate_dwo = False, bitcode_output = False, additional_compilation_inputs = additional_compilation_inputs, @@ -2297,12 +2326,25 @@ def _maybe_declare_gcno_file( ) return gcno_file +def _maybe_declare_trace_file( + ctx, + enable_trace, + object_file): + if not enable_trace: + return None + return _cc_internal.declare_other_output_file( + ctx = ctx, + output_name = paths.replace_extension(paths.basename(object_file.path), ".json"), + object_file = object_file, + ) + def _create_compile_action( *, action_construction_context = None, action_name = None, additional_compilation_inputs = None, additional_include_scanning_roots = [], + additional_outputs = [], cc_compilation_context = None, cc_toolchain = None, compile_build_variables = None, @@ -2329,6 +2371,7 @@ def _create_compile_action( action_name = action_name, additional_compilation_inputs = additional_compilation_inputs, additional_include_scanning_roots = additional_include_scanning_roots, + additional_outputs = additional_outputs, cc_compilation_context = cc_compilation_context, cc_toolchain = cc_toolchain, compile_build_variables = compile_build_variables, diff --git a/cc/private/compile/compile_build_variables.bzl b/cc/private/compile/compile_build_variables.bzl index f7575dac8..a483f58b8 100644 --- a/cc/private/compile/compile_build_variables.bzl +++ b/cc/private/compile/compile_build_variables.bzl @@ -343,7 +343,6 @@ def get_specific_compile_build_variables( # TODO: Blaze currently uses `gcov_gcno_file` to detect if code coverage # is enabled. It should use a different signal. result[_VARS.GCOV_GCNO_FILE] = "" - if dwo_file: result[_VARS.PER_OBJECT_DEBUG_INFO_FILE] = dwo_file if using_fission: diff --git a/cc/private/toolchain/unix_cc_toolchain_config.bzl b/cc/private/toolchain/unix_cc_toolchain_config.bzl index 1a4fcbe54..38e880576 100644 --- a/cc/private/toolchain/unix_cc_toolchain_config.bzl +++ b/cc/private/toolchain/unix_cc_toolchain_config.bzl @@ -1869,6 +1869,35 @@ def _impl(ctx): skip_virtual_includes_feature = feature(name = "skip_virtual_includes") + # Clang -ftime-trace support + trace_feature = feature( + name = "trace", + provides = ["trace"], + ) + + clang_trace_feature = feature( + name = "clang_trace", + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.preprocess_assemble, + ACTION_NAMES.c_compile, + ACTION_NAMES.cpp_compile, + ACTION_NAMES.cpp_module_compile, + ACTION_NAMES.objc_compile, + ACTION_NAMES.objcpp_compile, + ], + flag_groups = [ + flag_group( + flags = ["-ftime-trace"], + ), + ], + ), + ], + requires = [feature_set(features = ["trace"])], + enabled = True, + ) + # TODO(#8303): Mac crosstool should also declare every feature. if is_linux: # Linux artifact name patterns are the default. @@ -2016,6 +2045,11 @@ def _impl(ctx): extra_rules_based_features = depset(ctx.attr.extra_enabled_features + ctx.attr.extra_known_features) 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()]) + features.extend([ + trace_feature, + ]) + if ctx.attr.compiler == "clang": + features.append(clang_trace_feature) return cc_common.create_cc_toolchain_config_info( ctx = ctx, features = features, diff --git a/tests/integration/BUILD b/tests/integration/BUILD index c09ac8726..16022dc27 100644 --- a/tests/integration/BUILD +++ b/tests/integration/BUILD @@ -43,3 +43,14 @@ rules_cc_integration_test( target_compatible_with = VERSION_GATE, test_script = "cc_static_library_failure_tests.sh", ) + +rules_cc_integration_test( + name = "ftime_trace_tests", + timeout = "long", + target_compatible_with = select({ + "@platforms//os:macos": ["@platforms//:incompatible"], + "@platforms//os:windows": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + test_script = "ftime_trace_tests.sh", +) diff --git a/tests/integration/ftime_trace_tests.sh b/tests/integration/ftime_trace_tests.sh new file mode 100755 index 000000000..5d99d4f7e --- /dev/null +++ b/tests/integration/ftime_trace_tests.sh @@ -0,0 +1,76 @@ +set -euo pipefail + +source "$(rlocation rules_cc/tests/test_utils.sh)" +source "$(rlocation rules_cc/tests/unittest.bash)" + +function set_up() { + if is_windows; then + echo "Skipping on Windows." + return 1 + fi + + local -r clang="$(which clang || true)" + local -r clangxx="$(which clang++ || true)" + if [[ ! -x "$clang" || ! -x "$clangxx" ]]; then + echo "clang/clang++ not installed. Skipping test." + return 1 + fi + + add_to_bazelrc "common --repo_env=CC=$clang" + add_to_bazelrc "common --repo_env=CXX=$clangxx" +} + +function test_ftime_trace_outputs() { + # Bazel versions before 9 use the built-in native C++ rules, not rules_cc. + # This integration test exercises the rules_cc -ftime-trace implementation only. + local bazel_version="" + bazel_version="$(bazel version 2>/dev/null | awk '/^(Build label|Release label):/ { print $3; exit }')" + local bazel_major="${bazel_version%%.*}" + if [[ -z "$bazel_major" || "$bazel_major" -lt 9 ]]; then + echo "Bazel ${bazel_version:-unknown} is older than 9.0; native C++ rules are used instead of rules_cc. Skipping test." + return 0 + fi + + cat > BUILD << EOF +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +cc_library( + name = "lib_with_trace", + srcs = ["foo.cc"], + features = ["trace"], +) +EOF + + cat > foo.cc << EOF +int foo() { + return 42; +} +EOF + + bazel build //:lib_with_trace --output_groups=trace_files >& "$TEST_log" || fail "Build failed" + + trace_json="$(find bazel-bin/_objs/lib_with_trace -name '*.json' | head -1)" + if [[ -z "$trace_json" ]]; then + fail "Expected a Clang -ftime-trace JSON file under bazel-bin/_objs/lib_with_trace" + fi + + if [[ ! -s "$trace_json" ]]; then + fail "Trace file is empty: $trace_json" + fi + + python3 - "$trace_json" << 'PY' +import json +import sys + +path = sys.argv[1] +with open(path, encoding="utf-8") as input_file: + data = json.load(input_file) + +if "traceEvents" not in data: + raise SystemExit(f"missing traceEvents key in {path}") +if not isinstance(data["traceEvents"], list) or not data["traceEvents"]: + raise SystemExit(f"traceEvents is empty in {path}") +PY +} + +run_suite "Integration tests for Clang -ftime-trace with rules_cc"