Honor object extensions for ThinLTO merged artifacts (#853)

The ThinLTO merged object is hard-coded with an `.o` suffix even when the toolchain configures `.obj` for object files. This leaves clang-cl/MSVC action graphs with `.obj` compilation outputs but a `.o` indexing output and final-link input.

Derive the merged artifact suffix from the toolchain's `OBJECT_FILE` artifact category. Unix toolchains keep the default `.o`. Focused analysis coverage configures `.obj` and checks both the indexing output and final-link input.

Closes #853

COPYBARA_INTEGRATE_REVIEW=https://github.com/bazelbuild/rules_cc/pull/853 from cerisier:cerisier/thinlto-merged-object-extension 58053078f34d1f025c6d9a97b54ea060c006da37
PiperOrigin-RevId: 968965384
Change-Id: I7868b32b2f0368421029d578da04add3f016ebdc
diff --git a/cc/private/link/lto_indexing_action.bzl b/cc/private/link/lto_indexing_action.bzl
index 9e3e791..fe4a232 100644
--- a/cc/private/link/lto_indexing_action.bzl
+++ b/cc/private/link/lto_indexing_action.bzl
@@ -14,7 +14,7 @@
 """Functions that create LTO indexing action."""
 
 load("@bazel_features//:features.bzl", "bazel_features")
-load("//cc/common:cc_helper_internal.bzl", "root_relative_path")
+load("//cc/common:cc_helper_internal.bzl", "root_relative_path", artifact_category = "artifact_category_names")
 load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal")
 load("//cc/private/compile:lto_compilation_context.bzl", "get_minimized_bitcode_or_self")
 load("//cc/private/link:finalize_link_action.bzl", "finalize_link_action")
@@ -222,8 +222,12 @@
 
     # Create artifact for the merged object file, which is an object file that is created
     # during the LTO indexing step and needs to be passed to the final link.
+    object_file_extension = _cc_internal.get_artifact_name_extension_for_category(
+        cc_toolchain,
+        artifact_category.OBJECT_FILE,
+    )
     thinlto_merged_object_file = \
-        actions.declare_shareable_artifact(root_relative_path(output) + ".lto.merged.o")
+        actions.declare_shareable_artifact(root_relative_path(output) + ".lto.merged" + object_file_extension)
 
     action_outputs = \
         ([lto_artifact.imports for lto_artifact in all_lto_artifacts if lto_artifact.index] +
diff --git a/tests/cc/common/cc_binary_thin_lto_tests.bzl b/tests/cc/common/cc_binary_thin_lto_tests.bzl
index 5cbb1d1..0d2aa99 100644
--- a/tests/cc/common/cc_binary_thin_lto_tests.bzl
+++ b/tests/cc/common/cc_binary_thin_lto_tests.bzl
@@ -176,6 +176,32 @@
         ),
     )
 
+def _test_thin_lto_merged_object_uses_toolchain_extension(name, **kwargs):
+    util.helper_target(
+        cc_binary,
+        name = name + "/bin",
+        srcs = ["hello.cc"],
+    )
+    cc_analysis_test(
+        name = name,
+        impl = _test_thin_lto_merged_object_uses_toolchain_extension_impl,
+        target = name + "/bin",
+        test_features = ["thin_lto", "supports_start_end_lib"],
+        config_settings = {
+            str(Label("//tests/cc/testutil/toolchains:object_file_extension")): ".obj",
+        },
+        **kwargs
+    )
+
+def _test_thin_lto_merged_object_uses_toolchain_extension_impl(env, target):
+    merged_object = target.label.name.split("/")[-1] + ".lto.merged.obj"
+
+    index_action = env.expect.that_target(target).action_named("CppLTOIndexing")
+    index_action.outputs().contains_predicate(matching.file_basename_equals(merged_object))
+
+    link_action = env.expect.that_target(target).action_named("CppLink")
+    link_action.inputs().contains_predicate(matching.file_basename_equals(merged_object))
+
 def _test_thin_lto_linkshared(name, **kwargs):
     util.helper_target(
         cc_library,
@@ -2345,6 +2371,7 @@
 
     # These tests fail on Bazel 7 and 8, run only for Bazel 9+.
     if bazel_features.cc.cc_common_is_in_rules_cc:
+        tests.append(_test_thin_lto_merged_object_uses_toolchain_extension)
         tests.append(_test_thin_lto_linkshared)
         tests.append(_test_thin_lto_backend_env)
         tests.append(_test_linkstatic_cc_test)
diff --git a/tests/cc/testutil/toolchains/BUILD b/tests/cc/testutil/toolchains/BUILD
index 02fea23..45dd6af 100644
--- a/tests/cc/testutil/toolchains/BUILD
+++ b/tests/cc/testutil/toolchains/BUILD
@@ -1,4 +1,4 @@
-load("@bazel_skylib//rules:common_settings.bzl", "string_list_flag")
+load("@bazel_skylib//rules:common_settings.bzl", "string_flag", "string_list_flag")
 load("//cc:cc_library.bzl", "cc_library")
 load("//cc/toolchains:cc_toolchain.bzl", "cc_toolchain")
 load("//cc/toolchains:cc_toolchain_alias.bzl", "cc_toolchain_alias")
@@ -42,6 +42,11 @@
     build_setting_default = [],
 )
 
+string_flag(
+    name = "object_file_extension",
+    build_setting_default = "",
+)
+
 # "everything" is a filegroup that the implementation of --grte_top (more specifically, the
 # LibcTopLabelConverter in
 # third_party/bazel/src/main/java/com/google/devtools/build/lib/rules/cpp/CppOptions.java)
diff --git a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl
index 50fefe6..894e0fb 100644
--- a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl
+++ b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl
@@ -2053,6 +2053,9 @@
 
     for category, values in ctx.attr.artifact_name_patterns.items():
         artifact_name_patterns.append(_get_artifact_name_pattern(category, values[0], values[1]))
+    object_file_extension = ctx.attr._object_file_extension[BuildSettingInfo].value
+    if object_file_extension:
+        artifact_name_patterns.append(_get_artifact_name_pattern("object_file", "", object_file_extension))
 
     action_configs = []
 
@@ -2145,6 +2148,7 @@
         "make_variables": attr.string_dict(),
         "_with_features": attr.label(default = Label("//tests/cc/testutil/toolchains:with_features")),
         "_with_action_configs": attr.label(default = Label("//tests/cc/testutil/toolchains:with_action_configs")),
+        "_object_file_extension": attr.label(default = Label("//tests/cc/testutil/toolchains:object_file_extension")),
     },
     provides = [CcToolchainConfigInfo],
     executable = True,