Move dynamic library symlink actions to starlark. Should we implement https://github.com/bazelbuild/bazel/issues/26529 and just make sha256 available in starlark? PiperOrigin-RevId: 949247328 Change-Id: I005b0408ffdea7acb59bf75c66d9443940422d1f
diff --git a/cc/common/cc_helper_internal.bzl b/cc/common/cc_helper_internal.bzl index 6ced37f..6d0cb95 100644 --- a/cc/common/cc_helper_internal.bzl +++ b/cc/common/cc_helper_internal.bzl
@@ -287,6 +287,7 @@ ALWAYSLINK_LIBRARY = _ALWAYSLINK_LIBRARY, ALWAYSLINK_PIC_LIBRARY = _ALWAYSLINK_PIC_LIBRARY, SHARED_LIBRARY = _SHARED_LIBRARY, + INTERFACE_SHARED_LIBRARY = _INTERFACE_SHARED_LIBRARY, OBJECT_FILE = _OBJECT_FILE, PIC_OBJECT_FILE = _PIC_OBJECT_FILE, CC_AND_OBJC = _CC_AND_OBJC,
diff --git a/cc/private/cc_common.bzl b/cc/private/cc_common.bzl index 466f311..d78312c 100644 --- a/cc/private/cc_common.bzl +++ b/cc/private/cc_common.bzl
@@ -32,6 +32,7 @@ load("//cc/private/link:create_linker_input.bzl", "create_linker_input") load("//cc/private/link:create_linking_context_from_compilation_outputs.bzl", "create_linking_context_from_compilation_outputs") load("//cc/private/link:create_linkstamp.bzl", "create_linkstamp") +load("//cc/private/link:dynamic_library_symlink.bzl", "solib_symlink_action") load("//cc/private/link:link.bzl", "link") load("//cc/private/link:link_build_variables.bzl", "create_link_variables") load("//cc/private/link:lto_backends.bzl", "create_lto_backend_artifacts", "setup_common_lto_variables") @@ -717,12 +718,7 @@ def _solib_symlink_action(*, ctx, artifact, solib_directory, runtime_solib_dir_base): _cc_internal.check_private_api(allowlist = _PRIVATE_STARLARKIFICATION_ALLOWLIST) - return _cc_internal.solib_symlink_action( - ctx = ctx, - artifact = artifact, - solib_directory = solib_directory, - runtime_solib_dir_base = runtime_solib_dir_base, - ) + return solib_symlink_action(ctx, artifact, solib_directory, runtime_solib_dir_base) def _cc_toolchain_variables(*, vars): _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 b668e00..1af4169 100644 --- a/cc/private/link/cc_linking_helper.bzl +++ b/cc/private/link/cc_linking_helper.bzl
@@ -28,6 +28,11 @@ load("//cc/private/compile:cc_compilation_outputs.bzl", "EMPTY_COMPILATION_OUTPUTS") 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:dynamic_library_symlink.bzl", + "dynamic_library_soname", + "dynamic_library_symlink", +) load("//cc/private/link:lto_indexing_action.bzl", "create_lto_artifacts_and_lto_indexing_action") load("//cc/private/link:target_types.bzl", "LINKING_MODE", "LINK_TARGET_TYPE", "USE_ARCHIVER", "is_dynamic_library") @@ -289,7 +294,7 @@ # 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( + link_action_kwargs["linkopts"].append("-Wl,-soname=" + 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), @@ -335,7 +340,7 @@ impl_library_link_artifact = None if dynamic_link_type == LINK_TARGET_TYPE.DYNAMIC_LIBRARY and not neverlink and \ not feature_configuration.is_enabled("copy_dynamic_libraries_to_binary"): - impl_library_link_artifact = _cc_internal.dynamic_library_symlink( + impl_library_link_artifact = dynamic_library_symlink( actions, linker_output, cc_toolchain._solib_dir,
diff --git a/cc/private/link/cpp_link_action.bzl b/cc/private/link/cpp_link_action.bzl index f60376c..8842e78 100644 --- a/cc/private/link/cpp_link_action.bzl +++ b/cc/private/link/cpp_link_action.bzl
@@ -15,7 +15,7 @@ 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/private/link:dynamic_library_symlink.bzl", "dynamic_library_soname") 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") @@ -190,7 +190,7 @@ cc_toolchain, feature_configuration, output, - _cc_internal.dynamic_library_soname( + 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),
diff --git a/cc/private/link/create_library_to_link.bzl b/cc/private/link/create_library_to_link.bzl index 79e3566..3f61fb6 100644 --- a/cc/private/link/create_library_to_link.bzl +++ b/cc/private/link/create_library_to_link.bzl
@@ -19,6 +19,7 @@ load("//cc/common:cc_helper_internal.bzl", "is_versioned_shared_library", "path_contains_up_level_references") load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") load("//cc/private/compile:lto_compilation_context.bzl", _EMPTY_LTO = "EMPTY_LTO_COMPILATION_CONTEXT") +load("//cc/private/link:dynamic_library_symlink.bzl", "dynamic_library_symlink", "dynamic_library_symlink2") load("//cc/private/link:lto_backends.bzl", "create_shared_non_lto_artifacts") _warning = """ Don't use this field. It's intended for internal use and will be changed or removed @@ -211,9 +212,9 @@ if dynamic_library_symlink_path: if dynamic_library.short_path.startswith("_solib_"): fail("dynamic_library must not be a symbolic link in the solib directory. Got '%s'" % dynamic_library.short_path) - dynamic_library = _cc_internal.dynamic_library_symlink2(actions, dynamic_library, cc_toolchain._solib_dir, dynamic_library_symlink_path) + dynamic_library = dynamic_library_symlink2(actions, dynamic_library, cc_toolchain._solib_dir, dynamic_library_symlink_path) else: - dynamic_library = _cc_internal.dynamic_library_symlink(actions, dynamic_library, cc_toolchain._solib_dir, True, True) + dynamic_library = dynamic_library_symlink(actions, dynamic_library, cc_toolchain._solib_dir, True, True) resolved_symlink_interface_library = None if interface_library: @@ -226,9 +227,9 @@ if interface_library_symlink_path: if interface_library.short_path.startswith("_solib_"): fail("dynamic_library must not be a symbolic link in the solib directory. Got '%s'" % dynamic_library.short_path) - interface_library = _cc_internal.dynamic_library_symlink2(actions, interface_library, cc_toolchain._solib_dir, interface_library_symlink_path) + interface_library = dynamic_library_symlink2(actions, interface_library, cc_toolchain._solib_dir, interface_library_symlink_path) else: - interface_library = _cc_internal.dynamic_library_symlink(actions, interface_library, cc_toolchain._solib_dir, True, True) + interface_library = dynamic_library_symlink(actions, interface_library, cc_toolchain._solib_dir, True, True) identifier = static_library or pic_static_library or dynamic_library or interface_library if not identifier:
diff --git a/cc/private/link/dynamic_library_symlink.bzl b/cc/private/link/dynamic_library_symlink.bzl new file mode 100644 index 0000000..22882a8 --- /dev/null +++ b/cc/private/link/dynamic_library_symlink.bzl
@@ -0,0 +1,192 @@ +# 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. +"""Helper functions for creating solib and dynamic library symbolic links.""" + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//cc/common:cc_helper_internal.bzl", "extensions", "is_versioned_shared_library_extension_valid", "root_relative_path") +load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal") + +# TODO(bgorshenev): Remove this variable once we're sure these functions won't +# won't interfere with path mapping. +USE_STARLARK_SOLIB_SYMLINK = True + +def _escaped_path(path): + return path.replace("_", "_U").replace("/", "_S").replace("\\", "_B").replace(":", "_C").replace("@", "_A") + +def _label_to_string(label): + """Java and Starlark convert label to string slightly differently. Match Java's behavior.""" + s = str(label) + if s.startswith("@@//"): # buildifier: disable=canonical-repository + return s[2:] + if s.startswith("@//"): + return s[1:] + return s + +def _is_shared_library_filetype(basename): + if "." not in basename: + return True + if is_versioned_shared_library_extension_valid(basename): + return True + ext = basename[basename.rfind("."):] + if ext in extensions.SHARED_LIBRARY: + return True + if ext in extensions.INTERFACE_SHARED_LIBRARY: + return True + return False + +def _dynamic_library_soname(library_path, preserve_name, mnemonic): + if preserve_name: + return paths.basename(library_path) + else: + mnemonic_mangling = "" + if "ST-" in mnemonic: + st_index = mnemonic.find("ST-") + mnemonic_mangling = mnemonic[st_index:] + "_" + return "lib" + mnemonic_mangling + _escaped_path(library_path) + +def dynamic_library_soname(actions, library_path, preserve_name): + """Compute the SONAME to use for a dynamic library. + + This name is basically the name of the shared + library in its final symlinked location. + + Args: + actions: action construction context of rule requesting symlink + library_path: name of the shared library that needs to be mangled + preserve_name: whether to preserve the name of the library + + Returns: + soname to embed in the dynamic library + """ + if USE_STARLARK_SOLIB_SYMLINK and hasattr(_cc_internal, "maybe_hash_preserve_extension"): + ctx = _cc_internal.actions2ctx_cheat(actions) + mnemonic = ctx.bin_dir.path.split("/")[1] + return _dynamic_library_soname(library_path, preserve_name, mnemonic) + else: + return _cc_internal.dynamic_library_soname(actions, library_path, preserve_name) + +def _get_mangled_name(label, solib_dir, mnemonic, library_path, preserve_name, prefix_consumer): + escaped_rule_path = _escaped_path("_" + _label_to_string(label)) + soname = _dynamic_library_soname(library_path, preserve_name, mnemonic) + + if preserve_name: + parent_dir = paths.dirname(library_path) + escaped_library_path = _escaped_path("_" + parent_dir) + escaped_full_path = escaped_rule_path + "__" + escaped_library_path if prefix_consumer else escaped_library_path + mangled_dir = solib_dir + "/" + _cc_internal.maybe_hash_preserve_extension(escaped_full_path) + return mangled_dir + "/" + soname + else: + filename = escaped_rule_path + "__" + soname if prefix_consumer else soname + return solib_dir + "/" + _cc_internal.maybe_hash_preserve_extension(filename) + +def dynamic_library_symlink(actions, library, solib_directory, preserve_name, prefix_consumer): + """ Create dynamic library symlink. + + Replaces shared library artifact with mangled symlink and creates related symlink action. For + artifacts that should retain filename (e.g. libraries with SONAME tag), link is created to the + parent directory instead. + + This action is performed to minimize number of -rpath entries used during linking process + (by essentially "collecting" as many shared libraries as possible in the single directory), + since we will be paying quadratic price for each additional entry on the -rpath. + + Args: + actions: action construction context of rule requesting symlink + library: Shared library artifact that needs to be mangled. + solib_directory: String giving the solib directory + preserve_name: whether to preserve the name of the library + prefix_consumer: whether to prefix the output artifact name with the label of the consumer + Returns: + mangled symlink artifact. + """ + if USE_STARLARK_SOLIB_SYMLINK and hasattr(_cc_internal, "maybe_hash_preserve_extension"): + if not _is_shared_library_filetype(library.basename): + fail("Library '%s' does not match expected filetype" % library.basename) + if root_relative_path(library).startswith("_solib_"): + fail("Library '%s' is already in _solib_" % library.path) + ctx = _cc_internal.actions2ctx_cheat(actions) + label = ctx.label + mnemonic = ctx.bin_dir.path.split("/")[1] + mangled_name = _get_mangled_name( + label, + solib_directory, + mnemonic, + root_relative_path(library), + preserve_name, + prefix_consumer, + ) + symlink = ctx.actions.declare_shareable_artifact(mangled_name) + ctx.actions.symlink( + output = symlink, + target_file = library, + ) + return symlink + else: + return _cc_internal.dynamic_library_symlink(actions, library, solib_directory, preserve_name, prefix_consumer) + +def dynamic_library_symlink2(actions, library, solib_directory, path): + """Creates a symlink for a dynamic library with a rule-specified path. + + Args: + actions: action construction context of rule requesting symlink + library: Shared library artifact + solib_directory: String giving the solib directory + path: Symlink path underneath the solib directory. + Returns: + symlink artifact. + """ + if USE_STARLARK_SOLIB_SYMLINK and hasattr(_cc_internal, "maybe_hash_preserve_extension"): + if not _is_shared_library_filetype(library.basename): + fail("Library '%s' does not match expected filetype" % library.basename) + if not _is_shared_library_filetype(path.split("/")[-1]): + fail("Path '%s' does not match expected filetype" % path) + if root_relative_path(library).startswith("_solib_"): + fail("Library '%s' is already in _solib_" % library.path) + ctx = _cc_internal.actions2ctx_cheat(actions) + symlink_name = solib_directory + "/" + path + symlink = ctx.actions.declare_shareable_artifact(symlink_name) + ctx.actions.symlink( + output = symlink, + target_file = library, + ) + return symlink + else: + return _cc_internal.dynamic_library_symlink2(actions, library, solib_directory, path) + +def solib_symlink_action(ctx, artifact, solib_directory, runtime_solib_dir_base): + """Create a symlink for C++ runtime libraries without name or directory mangling. + + Args: + ctx: rule context of rule requesting symlink + artifact: Shared library artifact + solib_directory: String giving the solib directory, as defined by the toolchain. + runtime_solib_dir_base: Base directory for runtime symlinks, if the toolchain's needs to be overridden. + Returns: + symlink artifact. + """ + if USE_STARLARK_SOLIB_SYMLINK: + if not _is_shared_library_filetype(artifact.basename): + fail("Library '%s' does not match expected filetype" % artifact.basename) + if root_relative_path(artifact).startswith("_solib_"): + fail("Library '%s' is already in _solib_" % artifact.path) + solib_dir = runtime_solib_dir_base if runtime_solib_dir_base != None else solib_directory + symlink_name = solib_dir + "/" + artifact.basename + symlink = ctx.actions.declare_shareable_artifact(symlink_name) + ctx.actions.symlink( + output = symlink, + target_file = artifact, + ) + return symlink + else: + return _cc_internal.solib_symlink_action(ctx, artifact, solib_directory, runtime_solib_dir_base)