diff --git a/cc/common/cc_helper_internal.bzl b/cc/common/cc_helper_internal.bzl index 735e050f2..9149a2052 100644 --- a/cc/common/cc_helper_internal.bzl +++ b/cc/common/cc_helper_internal.bzl @@ -22,6 +22,21 @@ load("//cc/common:visibility.bzl", "PRIVATE_RULES_ALLOWLIST") load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") load("//cc/private:paths.bzl", "is_path_absolute") +_PATH_ESCAPE_REPLACEMENTS = { + "_": "_U", + "/": "_S", + "\\": "_B", + ":": "_C", + "@": "_A", +} + +def escape_path(path): + """Escapes a path for use as a filename.""" + return "".join([ + _PATH_ESCAPE_REPLACEMENTS.get(path[i], path[i]) + for i in range(len(path)) + ]) + def check_private_api(): _cc_internal.check_private_api(allowlist = PRIVATE_STARLARKIFICATION_ALLOWLIST, depth = 2) diff --git a/cc/private/cc_common.bzl b/cc/private/cc_common.bzl index 8db674306..be933451c 100644 --- a/cc/private/cc_common.bzl +++ b/cc/private/cc_common.bzl @@ -17,6 +17,7 @@ load( "//cc/common:cc_helper_internal.bzl", _CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES = "CREATE_COMPILE_ACTION_API_ALLOWLISTED_PACKAGES", _PRIVATE_STARLARKIFICATION_ALLOWLIST = "PRIVATE_STARLARKIFICATION_ALLOWLIST", + _escape_path = "escape_path", ) load("//cc/private:cc_info.bzl", "CcNativeLibraryInfo", "create_compilation_context", "create_debug_context", "create_linking_context", "create_module_map", "merge_cc_infos", "merge_compilation_contexts", "merge_debug_context", "merge_linking_contexts") load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") @@ -681,27 +682,12 @@ def _create_linkstamp(linkstamp, headers): _cc_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST) return create_linkstamp(linkstamp, headers) -_PATH_ESCAPE_REPLACEMENTS = { - "_": "_U", - "/": "_S", - "\\": "_B", - ":": "_C", - "@": "_A", -} - def _escape_label(*, label): _cc_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST) path = label.package + ":" + label.name if label.repo_name: path = label.repo_name + "@" + path - result = [] - for idx in range(len(path)): - c = path[idx] - result.append(_PATH_ESCAPE_REPLACEMENTS.get( - c, - c, # no escaping by default - )) - return "".join(result) + return _escape_path(path) def _cc_toolchain_features(*, toolchain_config_info, tools_directory): _cc_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST) diff --git a/cc/private/link/cc_linking_helper.bzl b/cc/private/link/cc_linking_helper.bzl index b668e008a..5bec38764 100644 --- a/cc/private/link/cc_linking_helper.bzl +++ b/cc/private/link/cc_linking_helper.bzl @@ -18,7 +18,6 @@ A module to create C/C++ link actions in a consistent way. load("@bazel_skylib//lib:paths.bzl", "paths") load( "//cc/common:cc_helper_internal.bzl", - "root_relative_path", "wrap_with_check_private_api", _use_pic_for_binaries = "use_pic_for_binaries", _use_pic_for_dynamic_libs = "use_pic_for_dynamic_libs", @@ -29,6 +28,7 @@ load("//cc/private/compile:cc_compilation_outputs.bzl", "EMPTY_COMPILATION_OUTPU load("//cc/private/link:cpp_link_action.bzl", "link_action") load("//cc/private/link:create_library_to_link.bzl", "make_library_to_link") load("//cc/private/link:lto_indexing_action.bzl", "create_lto_artifacts_and_lto_indexing_action") +load("//cc/private/link:solib.bzl", "dynamic_library_soname") load("//cc/private/link:target_types.bzl", "LINKING_MODE", "LINK_TARGET_TYPE", "USE_ARCHIVER", "is_dynamic_library") # TODO(b/331164666): remove alwayslink @@ -289,11 +289,9 @@ def _create_dynamic_link_actions( # TODO(b/28946988): Remove this hard-coded flag. if not feature_configuration.is_enabled("targets_windows") and not feature_configuration.is_enabled("set_soname"): - link_action_kwargs["linkopts"].append("-Wl,-soname=" + _cc_internal.dynamic_library_soname( - actions, - # Must match https://github.com/bazelbuild/bazel/blob/795af54db5c348af5ca8b2961a982b399206ea20/src/main/java/com/google/devtools/build/lib/rules/cpp/SolibSymlinkAction.java#L169. - root_relative_path(linker_output), - dynamic_link_type != LINK_TARGET_TYPE.NODEPS_DYNAMIC_LIBRARY, + link_action_kwargs["linkopts"].append("-Wl,-soname=" + dynamic_library_soname( + linker_output, + preserve_name = dynamic_link_type != LINK_TARGET_TYPE.NODEPS_DYNAMIC_LIBRARY, )) mnemonic = "ObjcLink" if dynamic_link_type == LINK_TARGET_TYPE.OBJC_EXECUTABLE else None diff --git a/cc/private/link/cpp_link_action.bzl b/cc/private/link/cpp_link_action.bzl index f60376c56..893fdbb91 100644 --- a/cc/private/link/cpp_link_action.bzl +++ b/cc/private/link/cpp_link_action.bzl @@ -14,11 +14,11 @@ """Functions that create C++ link action.""" load("@bazel_skylib//lib:paths.bzl", "paths") -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/common:cc_helper_internal.bzl", artifact_category = "artifact_category_names") load("//cc/private/link:finalize_link_action.bzl", "finalize_link_action") load("//cc/private/link:link_build_variables.bzl", "setup_linking_variables") load("//cc/private/link:lto_backends.bzl", "create_shared_non_lto_artifacts") +load("//cc/private/link:solib.bzl", "dynamic_library_soname") load("//cc/private/link:target_types.bzl", "LINK_TARGET_TYPE", "USE_ARCHIVER", "USE_LINKER", "is_dynamic_library") load("//cc/private/rules_impl:native_cc_common.bzl", _cc_common_internal = "native_cc_common") @@ -190,11 +190,9 @@ def link_action( cc_toolchain, feature_configuration, output, - _cc_internal.dynamic_library_soname( - actions, - # Must match https://github.com/bazelbuild/bazel/blob/795af54db5c348af5ca8b2961a982b399206ea20/src/main/java/com/google/devtools/build/lib/rules/cpp/SolibSymlinkAction.java#L169. - root_relative_path(output), - link_type != LINK_TARGET_TYPE.NODEPS_DYNAMIC_LIBRARY, + dynamic_library_soname( + output, + preserve_name = link_type != LINK_TARGET_TYPE.NODEPS_DYNAMIC_LIBRARY, ), interface_output, thinlto_param_file, diff --git a/cc/private/link/solib.bzl b/cc/private/link/solib.bzl new file mode 100644 index 000000000..e8bfab1c4 --- /dev/null +++ b/cc/private/link/solib.bzl @@ -0,0 +1,37 @@ +# Copyright 2026 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Functions for shared library symlinks.""" + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//cc/common:cc_helper_internal.bzl", "escape_path", "root_relative_path") + +def dynamic_library_soname(library, preserve_name): + """Returns the SONAME for a dynamic library. + + Args: + library: The dynamic library output. + preserve_name: Whether to preserve the output basename. + + Returns: + The dynamic library SONAME. + """ + if preserve_name: + return library.basename + + configuration_mnemonic = paths.basename(paths.dirname(library.root.path)) + transition_index = configuration_mnemonic.find("ST-") + mnemonic_mangling = "" + if transition_index != -1: + mnemonic_mangling = configuration_mnemonic[transition_index:] + "_" + return "lib" + mnemonic_mangling + escape_path(root_relative_path(library)) diff --git a/tests/cc/common/cc_binary_configured_target_tests.bzl b/tests/cc/common/cc_binary_configured_target_tests.bzl index 3c79acf84..5989350f6 100644 --- a/tests/cc/common/cc_binary_configured_target_tests.bzl +++ b/tests/cc/common/cc_binary_configured_target_tests.bzl @@ -1023,6 +1023,61 @@ def _test_app_linking_dynamic_impl(env, target): "-linfrastructure1_opt", ]).in_order() +def _test_runtime_solib_name_preserves_output_basename(name, **kwargs): + util.helper_target( + cc_binary, + name = name + "/app_with_underscore", + srcs = ["main.cc"], + ) + cc_analysis_test( + name = name, + impl = _test_runtime_solib_name_preserves_output_basename_impl, + target = name + "/app_with_underscore", + test_features = [FEATURE_NAMES.runtime_solib_name], + **kwargs + ) + +def _test_runtime_solib_name_preserves_output_basename_impl(env, target): + link_action = link_action_subject.from_target(env, target) + link_action.argv().contains("--runtime_solib_name=app_with_underscore") + +def _test_runtime_solib_name_mangles_output_path(name, **kwargs): + util.helper_target( + cc_library, + name = name + "/lib_with_underscore", + srcs = ["a.cc"], + ) + cc_analysis_test( + name = name, + impl = _test_runtime_solib_name_mangles_output_path_impl, + target = name + "/lib_with_underscore", + test_features = [ + FEATURE_NAMES.runtime_solib_name, + FEATURE_NAMES.supports_dynamic_linker, + FEATURE_NAMES.supports_pic, + ], + **kwargs + ) + +def _test_runtime_solib_name_mangles_output_path_impl(env, target): + dynamic_library_actions = [] + for action in target[TestingAspectInfo].actions: + if action.mnemonic == "CppLink" and any([output.extension == "so" for output in action.outputs.to_list()]): + dynamic_library_actions.append(action) + + env.expect.that_collection(dynamic_library_actions).has_size(1) + if not dynamic_library_actions: + return + + test_name = env.ctx.label.name.replace("_", "_U") + expected_suffix = "_tests_Scc_Scommon_S{}_Sliblib_Uwith_Uunderscore.so".format(test_name) + env.expect.that_collection(dynamic_library_actions[0].argv).contains_predicate( + matching.custom( + "starts with the transitioned configuration and ends with the escaped output path", + lambda arg: arg.startswith("--runtime_solib_name=libST-") and arg.endswith(expected_suffix), + ), + ) + def _test_so_in_srcs(name, **kwargs): util.helper_target( cc_binary, @@ -1819,6 +1874,8 @@ def cc_binary_configured_target_tests(name): _test_linkopts_fake_diamond, _test_app_linking_static, _test_app_linking_dynamic, + _test_runtime_solib_name_preserves_output_basename, + _test_runtime_solib_name_mangles_output_path, _test_so_in_srcs, _test_transitive_libs_are_collected, _test_transitive_linkstamps_are_collected, diff --git a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl index dc004b28d..ae0a5e309 100644 --- a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl +++ b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl @@ -1188,6 +1188,25 @@ _runtime_library_search_directories_feature = feature( ], ) +_runtime_solib_name_feature = feature( + name = FEATURE_NAMES.runtime_solib_name, + flag_sets = [ + flag_set( + actions = [ + ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ], + flag_groups = [ + flag_group( + expand_if_available = "runtime_solib_name", + flags = ["--runtime_solib_name=%{runtime_solib_name}"], + ), + ], + ), + ], +) + _uses_ifso_variables_feature = feature( name = FEATURE_NAMES.uses_ifso_variables, enabled = True, @@ -1397,6 +1416,7 @@ _feature_name_to_feature = { FEATURE_NAMES.check_additional_variables: _check_additional_variables_feature, FEATURE_NAMES.library_search_directories: _library_search_directories_feature, FEATURE_NAMES.runtime_library_search_directories: _runtime_library_search_directories_feature, + FEATURE_NAMES.runtime_solib_name: _runtime_solib_name_feature, FEATURE_NAMES.generate_submodules: _generate_submodules_feature, FEATURE_NAMES.uses_ifso_variables: _uses_ifso_variables_feature, FEATURE_NAMES.def_feature: _def_feature, diff --git a/tests/cc/testutil/toolchains/features.bzl b/tests/cc/testutil/toolchains/features.bzl index 50e9930e6..d646e79bc 100644 --- a/tests/cc/testutil/toolchains/features.bzl +++ b/tests/cc/testutil/toolchains/features.bzl @@ -86,6 +86,7 @@ FEATURE_NAMES = struct( check_additional_variables = "check_additional_variables_feature", library_search_directories = "library_search_directories", runtime_library_search_directories = "runtime_library_search_directories", + runtime_solib_name = "runtime_solib_name", uses_ifso_variables = "uses_ifso_variables", def_feature = "def", strip_debug_symbols = "strip_debug_symbols",