Rewrite LibrariesToLinkCollector to Starlark
Intricate code with a lot of potential for suttle breakages, thus keeping on first iteration very close to native implementation.
This codes connects convert_linker_inputs.bzl with link_build_variables.bzl: LibraryToLink objects are first converted to LegacyLinkerInputs, then to LibraryToLinkValues.
PiperOrigin-RevId: 634761527
Change-Id: I2bf5ecd60c9728cd3c00c3cf1bd10ccd1f854b96
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java
index 4632713..100aeef 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcStarlarkInternal.java
@@ -17,6 +17,7 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.devtools.build.lib.skyframe.BzlLoadValue.keyForBuild;
+import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.docgen.annot.DocCategory;
@@ -46,6 +47,7 @@
import com.google.devtools.build.lib.packages.StarlarkProvider;
import com.google.devtools.build.lib.packages.Types;
import com.google.devtools.build.lib.rules.cpp.CcLinkingContext.Linkstamp;
+import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.LibraryToLinkValue;
import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.SequenceBuilder;
import com.google.devtools.build.lib.rules.cpp.CcToolchainVariables.VariableValue;
import com.google.devtools.build.lib.starlarkbuildapi.NativeComputedDefaultApi;
@@ -489,6 +491,84 @@
}
@StarlarkMethod(
+ name = "for_static_library",
+ documented = false,
+ parameters = {@Param(name = "name"), @Param(name = "is_whole_archive")})
+ public LibraryToLinkValue forStaticLibrary(String name, boolean isWholeArchive) {
+ return LibraryToLinkValue.forStaticLibrary(name, isWholeArchive);
+ }
+
+ @StarlarkMethod(
+ name = "for_object_file_group",
+ documented = false,
+ parameters = {@Param(name = "files"), @Param(name = "is_whole_archive")})
+ public LibraryToLinkValue forObjectFileGroup(Sequence<?> files, boolean isWholeArchive)
+ throws EvalException {
+ return LibraryToLinkValue.forObjectFileGroup(
+ ImmutableList.copyOf(Sequence.cast(files, Artifact.class, "files")), isWholeArchive);
+ }
+
+ @StarlarkMethod(
+ name = "for_object_file",
+ documented = false,
+ parameters = {@Param(name = "name"), @Param(name = "is_whole_archive")})
+ public LibraryToLinkValue forObjectFile(String name, boolean isWholeArchive) {
+ return LibraryToLinkValue.forObjectFile(name, isWholeArchive);
+ }
+
+ @StarlarkMethod(
+ name = "for_interface_library",
+ documented = false,
+ parameters = {@Param(name = "name")})
+ public LibraryToLinkValue forInterfaceLibrary(String name) throws EvalException {
+ return LibraryToLinkValue.forInterfaceLibrary(name);
+ }
+
+ @StarlarkMethod(
+ name = "for_dynamic_library",
+ documented = false,
+ parameters = {@Param(name = "name")})
+ public LibraryToLinkValue forDynamicLibrary(String name) throws EvalException {
+ return LibraryToLinkValue.forDynamicLibrary(name);
+ }
+
+ @StarlarkMethod(
+ name = "for_versioned_dynamic_library",
+ documented = false,
+ parameters = {@Param(name = "name"), @Param(name = "path")})
+ public LibraryToLinkValue forVersionedDynamicLibrary(String name, String path)
+ throws EvalException {
+ return LibraryToLinkValue.forVersionedDynamicLibrary(name, path);
+ }
+
+ @StarlarkMethod(
+ name = "simple_linker_input",
+ documented = false,
+ parameters = {
+ @Param(name = "input"),
+ @Param(name = "artifact_category", defaultValue = "'object_file'"),
+ @Param(name = "disable_whole_archive", defaultValue = "False")
+ })
+ public LegacyLinkerInput simpleLinkerInput(
+ Artifact input, String artifactCategory, boolean disableWholeArchive) {
+ return LegacyLinkerInputs.simpleLinkerInput(
+ input,
+ ArtifactCategory.valueOf(Ascii.toUpperCase(artifactCategory)),
+ /* disableWholeArchive= */ disableWholeArchive,
+ input.getRootRelativePathString());
+ }
+
+ @StarlarkMethod(
+ name = "linkstamp_linker_input",
+ documented = false,
+ parameters = {
+ @Param(name = "input"),
+ })
+ public LegacyLinkerInput linkstampLinkerInput(Artifact input) {
+ return LegacyLinkerInputs.linkstampLinkerInput(input);
+ }
+
+ @StarlarkMethod(
name = "library_linker_input",
documented = false,
parameters = {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainVariables.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainVariables.java
index 501452e..b4c45ce 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainVariables.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcToolchainVariables.java
@@ -41,6 +41,7 @@
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkValue;
/**
* Configured build variables usable by the toolchain configuration.
@@ -651,7 +652,8 @@
* significantly reduces memory overhead.
*/
@Immutable
- public abstract static class LibraryToLinkValue extends VariableValueAdapter {
+ public abstract static class LibraryToLinkValue extends VariableValueAdapter
+ implements StarlarkValue {
private static final Interner<LibraryToLinkValue> interner = BlazeInterners.newWeakInterner();
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
index 420ea3f..2a316f2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -489,6 +489,12 @@
return cppOptions.useStartEndLib;
}
+ @StarlarkMethod(name = "start_end_lib", documented = false, useStarlarkThread = true)
+ public boolean startEndLibIsRequestedForStarlark(StarlarkThread thread) throws EvalException {
+ CcModule.checkPrivateStarlarkificationAllowlist(thread);
+ return cppOptions.useStartEndLib;
+ }
+
/** @return value from --compiler option, null if the option was not passed. */
@Nullable
public String getCompilerFromOptions() {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java
index f19ddb3..dd261d9 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/LibrariesToLinkCollector.java
@@ -39,7 +39,8 @@
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;
-/** Class that goes over linker inputs and produces {@link LibraryToLinkValue}s */
+// LINT.IfChange
+/** Class that goes over legacy linker inputs and produces {@link LibraryToLinkValue}s */
public class LibrariesToLinkCollector {
private static final OsPathPolicy OS = OsPathPolicy.getFilePathOs();
@@ -780,3 +781,4 @@
return allowLtoIndexing && !a.getRootRelativePath().startsWith(sharedNonLtoObjRootPrefix);
}
}
+// LINT.ThenChange(//src/main/starlark/builtins_bzl/common/cc/link/libraries_to_link_collector.bzl)
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
index 17b4513..78a47e9 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_helper.bzl
@@ -15,7 +15,12 @@
"""Utility functions for C++ rules."""
load(":common/cc/cc_common.bzl", "cc_common")
-load(":common/cc/cc_helper_internal.bzl", "should_create_per_object_debug_info", _artifact_category = "artifact_category")
+load(
+ ":common/cc/cc_helper_internal.bzl",
+ "is_versioned_shared_library_extension_valid",
+ "should_create_per_object_debug_info",
+ _artifact_category = "artifact_category",
+)
load(":common/cc/cc_info.bzl", "CcInfo")
load(":common/objc/objc_common.bzl", "objc_common")
load(":common/objc/semantics.bzl", objc_semantics = "semantics")
@@ -56,7 +61,7 @@
def _check_file_extension(file, allowed_extensions, allow_versioned_shared_libraries):
extension = "." + file.extension
- if _matches_extension(extension, allowed_extensions) or (allow_versioned_shared_libraries and _is_versioned_shared_library_extension_valid(file.path)):
+ if _matches_extension(extension, allowed_extensions) or (allow_versioned_shared_libraries and is_versioned_shared_library_extension_valid(file.path)):
return True
return False
@@ -507,22 +512,6 @@
shared_libraries,
)
-def _is_versioned_shared_library_extension_valid(shared_library_name):
- # validate agains the regex "^.+\\.((so)|(dylib))(\\.\\d\\w*)+$",
- # must match VERSIONED_SHARED_LIBRARY.
- for ext in (".so.", ".dylib."):
- name, _, version = shared_library_name.rpartition(ext)
- if name and version:
- version_parts = version.split(".")
- for part in version_parts:
- if not part[0].isdigit():
- return False
- for c in part[1:].elems():
- if not (c.isalnum() or c == "_"):
- return False
- return True
- return False
-
# NOTE: Prefer to use _is_valid_shared_library_artifact() instead of this method since
# it has better performance (checking for extension in a short list rather than multiple
# string.endswith() checks)
@@ -533,7 +522,7 @@
shared_library_name.endswith(".wasm")):
return True
- return _is_versioned_shared_library_extension_valid(shared_library_name)
+ return is_versioned_shared_library_extension_valid(shared_library_name)
_SHARED_LIBRARY_EXTENSIONS = ["so", "dll", "dylib", "wasm"]
@@ -541,7 +530,7 @@
if (shared_library.extension in _SHARED_LIBRARY_EXTENSIONS):
return True
- return _is_versioned_shared_library_extension_valid(shared_library.basename)
+ return is_versioned_shared_library_extension_valid(shared_library.basename)
def _get_providers(deps, provider):
providers = []
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_helper_internal.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_helper_internal.bzl
index 88c7a4e..fe232e5 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_helper_internal.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_helper_internal.bzl
@@ -42,3 +42,30 @@
def should_create_per_object_debug_info(feature_configuration, cpp_configuration):
return cpp_configuration.fission_active_for_current_compilation_mode() and \
feature_configuration.is_enabled("per_object_debug_info")
+
+def is_versioned_shared_library_extension_valid(shared_library_name):
+ # validate against the regex "^.+\\.((so)|(dylib))(\\.\\d\\w*)+$",
+ # must match VERSIONED_SHARED_LIBRARY.
+ for ext in (".so.", ".dylib."):
+ name, _, version = shared_library_name.rpartition(ext)
+ if name and version:
+ version_parts = version.split(".")
+ for part in version_parts:
+ if not part[0].isdigit():
+ return False
+ for c in part[1:].elems():
+ if not (c.isalnum() or c == "_"):
+ return False
+ return True
+ return False
+
+def is_shared_library(file):
+ return file.extension in ["so", "dylib", "dll", "pyd", "wasm", "tgt", "vpi"]
+
+def is_versioned_shared_library(file):
+ # Because regex matching can be slow, we first do a quick check for ".so." and ".dylib."
+ # substring before risking the full-on regex match. This should eliminate the performance
+ # hit on practically every non-qualifying file type.
+ if ".so." not in file.basename and ".dylib." not in file.basename:
+ return False
+ return is_versioned_shared_library_extension_valid(file.basename)
diff --git a/src/main/starlark/builtins_bzl/common/cc/link/libraries_to_link_collector.bzl b/src/main/starlark/builtins_bzl/common/cc/link/libraries_to_link_collector.bzl
new file mode 100644
index 0000000..149f9bf
--- /dev/null
+++ b/src/main/starlark/builtins_bzl/common/cc/link/libraries_to_link_collector.bzl
@@ -0,0 +1,757 @@
+# Copyright 2024 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.
+"""Goes over LegacyLinkerInputs and produces LibraryToLinkValue-s."""
+
+load(":common/cc/cc_helper_internal.bzl", "artifact_category", "is_shared_library", "is_versioned_shared_library")
+load(":common/cc/link/target_types.bzl", "LINK_TARGET_TYPE", "is_dynamic_library")
+load(":common/paths.bzl", "paths")
+
+cc_internal = _builtins.internal.cc_internal
+
+LINKING_MODE = struct(
+ STATIC = "static",
+ DYNAMIC = "dynamic",
+)
+
+# TODO(b/338618120): This code is doing 2 distinct tasks and should be split accordingly: converting
+# LegacyLinkerInputs to LibraryToLinkValues and collecting rpaths for dynamic libraries.
+
+# TODO(b/338618120): Refine the signature of collect_libraries_to_link. Large objects are passed in
+# just to determine a single property, for example link_type and linking_mode are passed in, just to
+# determine need_toolchain_libraries_rpath. Refining the signature will increase readability.
+def collect_libraries_to_link(
+ linker_inputs,
+ cc_toolchain,
+ feature_configuration,
+ output,
+ dynamic_library_solib_symlink_output,
+ link_type,
+ linking_mode,
+ is_native_deps,
+ need_whole_archive,
+ solib_dir,
+ toolchain_libraries_solib_dir,
+ allow_lto_indexing,
+ lto_mapping,
+ workspace_name):
+ """Goes over LegacyLinkerInputs and produces LibraryToLinkValue-s and rpaths.
+
+ LegacyLinkerInputs are produced by convert_linker_inputs.bzl. LibraryToLinkValues are consumed
+ by link_build_variables.bzl.
+
+ When linking a shared library fully or mostly static then we need to link in *all* dependent
+ files, not just what the shared library needs for its own code. This is done by wrapping all
+ objects/libraries with -Wl,-whole-archive and -Wl,-no-whole-archive. For this case the
+ globalNeedWholeArchive parameter must be set to true. Otherwise only library objects (.lo) need
+ to be wrapped with -Wl,-whole-archive and -Wl,-no-whole-archive.
+
+ Args:
+ linker_inputs: (list[LegacyLinkerInput]) Linker inputs
+ cc_toolchain: cc_toolchain providing some extra information in the conversion.
+ feature_configuration: Feature configuration to be queried.
+ output: (File) The linker's output.
+ dynamic_library_solib_symlink_output: (None|File) Symlink to the dynamic library being created.
+ link_type: (LINK_TARGET_TYPE) The type of ELF file to be created (.a, .so, .lo, executable).
+ linking_mode: ("static"|"dynamic") Linking mode.
+ is_native_deps: (bool) Is this link action is used for a native dependency library.
+ need_whole_archive: (bool) Whether we need to use whole-archive for the link.
+ solib_dir: (str) solib directory.
+ toolchain_libraries_solib_dir: (str) Directory where toolchain stores language-runtime libraries (libstdc++, libc++ ...).
+ allow_lto_indexing: (bool) Is LTO indexing being done.
+ lto_mapping: (dict[File, File]) Map from bitcode files to object files. Used to replace all linker inputs.
+ workspace_name: (str) Workspace name. To support legacy code.
+
+ Returns:
+ ({libraries_to_link: list[LibraryToLinkValue],
+ expanded_linker_inputs: list[LegacyLinkerInput],
+ library_search_directories: depset[str],
+ all_runtime_library_search_directories: depset[str]})
+
+ - Returned libraries_to_link are passed in this form to link_build_variables.
+ - expanded_linker_inputs are all the files that will be consumed by the linker (if
+ start-end library is used, then it contains object files, otherwise an archive)
+ - library_search_directories are absolute directories of all dynamic libraries
+ - all_runtime_library_search_directories are directories of all dynamic libraries
+
+ Both depsets of directories are exposed to the link_build_variables.
+
+ """
+ need_toolchain_libraries_rpath = (
+ toolchain_libraries_solib_dir and
+ (is_dynamic_library(link_type) or
+ (link_type == LINK_TARGET_TYPE.EXECUTABLE and linking_mode == LINKING_MODE.DYNAMIC))
+ )
+
+ # Collect LibrariesToLink
+ library_search_directories = []
+ rpath_roots_for_explicit_so_deps = []
+ expanded_linker_inputs = []
+
+ # List of command line parameters that need to be placed *outside* of
+ # --whole-archive ... --no-whole-archive.
+ libraries_to_link = []
+
+ # Calculate the correct relative value for the "-rpath" link option (which sets
+ # the search path for finding shared libraries).
+ solib_dir_path_string = cc_toolchain._solib_dir
+ if is_native_deps and cc_toolchain._cpp_configuration.share_native_deps():
+ # For shared native libraries, special symlinking is applied to ensure C++
+ # toolchain libraries are available under $ORIGIN/_solib_[arch]. So we set the RPATH to find
+ # them.
+
+ # Note that we have to do this because $ORIGIN points to different paths for
+ # different targets. In other words, blaze-bin/d1/d2/d3/a_shareddeps.so and
+ # blaze-bin/d4/b_shareddeps.so have different path depths. The first could
+ # reference a standard blaze-bin/_solib_[arch] via $ORIGIN/../../../_solib[arch],
+ # and the second could use $ORIGIN/../_solib_[arch]. But since this is a shared
+ # artifact, both are symlinks to the same place, so
+ # there's no *one* RPATH setting that fits all targets involved in the sharing.
+ potential_solib_parents = []
+ rpath_roots = [solib_dir_path_string + "/"]
+ else:
+ potential_solib_parents = _find_potential_solib_parents(output, dynamic_library_solib_symlink_output, workspace_name)
+ rpath_roots = [exec_root + solib_dir_path_string + "/" for exec_root in potential_solib_parents]
+
+ lto_map = dict(lto_mapping) # We make a copy, to check all mapped files were used
+
+ # include_solib_dir: bool, include_toolchain_libraries_solib_dir: bool
+ # TODO(b/338618120): instead of returning include_solib_dir, and the paths inside _add_linker_inputs
+ include_solib_dir, include_toolchain_libraries_solib_dir = _add_linker_inputs(
+ linker_inputs,
+ cc_toolchain,
+ feature_configuration,
+ need_whole_archive,
+ solib_dir,
+ toolchain_libraries_solib_dir,
+ rpath_roots,
+ allow_lto_indexing,
+ lto_map,
+ # Outputs:
+ libraries_to_link,
+ expanded_linker_inputs,
+ library_search_directories,
+ rpath_roots_for_explicit_so_deps,
+ )
+
+ if lto_map:
+ fail("Still have LTO objects left: %s" % lto_map)
+
+ # Remove repetitions
+ rpath_roots_for_explicit_so_deps = depset(rpath_roots_for_explicit_so_deps).to_list()
+
+ # rpath ordering matters for performance; first add the one where most libraries are found.
+ direct_runtime_library_search_directories = []
+ if include_solib_dir:
+ direct_runtime_library_search_directories.extend(rpath_roots)
+ direct_runtime_library_search_directories.extend(rpath_roots_for_explicit_so_deps)
+
+ transitive_runtime_library_search_directories = []
+ if include_toolchain_libraries_solib_dir:
+ transitive_runtime_library_search_directories = [
+ _collect_toolchain_runtime_library_search_directories(
+ cc_toolchain,
+ output,
+ potential_solib_parents,
+ need_toolchain_libraries_rpath,
+ toolchain_libraries_solib_dir,
+ is_native_deps,
+ workspace_name,
+ ),
+ ]
+ all_runtime_library_search_directories = depset(
+ order = "topological",
+ direct = direct_runtime_library_search_directories,
+ transitive = transitive_runtime_library_search_directories,
+ )
+
+ return struct(
+ libraries_to_link = libraries_to_link,
+ expanded_linker_inputs = expanded_linker_inputs,
+ library_search_directories = depset(library_search_directories),
+ all_runtime_library_search_directories = all_runtime_library_search_directories,
+ )
+
+def _add_linker_inputs(
+ linker_inputs,
+ cc_toolchain,
+ feature_configuration,
+ need_whole_archive,
+ solib_dir,
+ toolchain_libraries_solib_dir,
+ rpath_roots,
+ allow_lto_indexing,
+ lto_map,
+ # Outputs:
+ libraries_to_link,
+ expanded_linker_inputs,
+ library_search_directories,
+ rpath_entries):
+ """
+ Goes over all linker_inputs transforming them and collecting rpath_roots.
+
+ Args:
+ linker_inputs: (list[LegacyLinkerInput]) Linker inputs
+ cc_toolchain: cc_toolchain providing some extra information in the conversion.
+ feature_configuration: Feature configuration to be queried.
+ need_whole_archive: (bool) Whether we need to use whole-archive for the link.
+ solib_dir: (str) solib directory.
+ toolchain_libraries_solib_dir: (str) Directory where toolchain stores language-runtime libraries (libstdc++, libc++ ...).
+ rpath_roots: (list[str]) rpath roots (for example solib_dir)
+ allow_lto_indexing: bool) Is LTO indexing being done.
+ lto_map: (dict[File, File]) Map from bitcode files to object files. Used to replace all linker inputs.
+ libraries_to_link: (list[LibraryToLinkValue]) Output collecting libraries to link.
+ expanded_linker_inputs: (list[LegacyLinkerInput]) Output collecting expanded linker inputs.
+ library_search_directories: (list[str]) Output collecting library search directories.
+ rpath_entries: (list[str]) Output collecting rpaths.
+
+ Returns:
+ (include_solib_dir: bool, include_toolchain_libraries_solib_dir: bool)
+ """
+
+ include_solib_dir, include_toolchain_libraries_solib_dir = False, False
+ linked_libraries_paths = {} # :dict[str, str]
+
+ # TODO(b/331164666): Remove CppHelper.getArchiveType
+ use_start_end_lib = (cc_toolchain._cpp_configuration.start_end_lib and
+ feature_configuration.is_enabled("supports_start_end_lib"))
+
+ for input in linker_inputs:
+ if (input.artifact_category in
+ [artifact_category.DYNAMIC_LIBRARY, artifact_category.INTERFACE_LIBRARY]):
+ original_lib_dir = input.original_file.dirname
+ library_identifier = input.library_identifier
+ previous_lib_dir = linked_libraries_paths.get(library_identifier, None)
+
+ if not previous_lib_dir:
+ linked_libraries_paths[library_identifier] = original_lib_dir
+ elif previous_lib_dir != original_lib_dir:
+ fail(("You are trying to link the same dynamic library %s built in a different" +
+ " configuration. Previously registered instance had path %s, current one" +
+ " has path %s") %
+ (library_identifier, previous_lib_dir, original_lib_dir))
+
+ lib_dir = input.file.dirname
+
+ # When COPY_DYNAMIC_LIBRARIES_TO_BINARY is enabled, dynamic libraries are not symlinked
+ # under solib_dir, so don't check it and don't include solib_dir.
+ if not feature_configuration.is_enabled("copy_dynamic_libraries_to_binary"):
+ # The first fragment is bazel-out, and the second may contain a configuration mnemonic.
+ # We should always add the default solib dir because that's where libraries will be found
+ # e.g., in remote execution, so we ignore the first two fragments.
+ if lib_dir.split("/")[2] == solib_dir.split("/")[2]:
+ include_solib_dir = True
+ if lib_dir == toolchain_libraries_solib_dir:
+ include_toolchain_libraries_solib_dir = True
+
+ _add_dynamic_input_link_options(
+ input,
+ feature_configuration,
+ solib_dir,
+ toolchain_libraries_solib_dir,
+ rpath_roots,
+ # Outputs:
+ libraries_to_link,
+ expanded_linker_inputs,
+ library_search_directories,
+ rpath_entries, # = rpath_roots_for_explicit_so_deps,
+ )
+ else:
+ _add_static_input_link_options(
+ input,
+ feature_configuration,
+ use_start_end_lib,
+ need_whole_archive,
+ lto_map,
+ allow_lto_indexing,
+ # Outputs:
+ libraries_to_link,
+ expanded_linker_inputs,
+ )
+
+ return include_solib_dir, include_toolchain_libraries_solib_dir
+
+def _add_dynamic_input_link_options(
+ input,
+ feature_configuration,
+ solib_dir,
+ toolchain_libraries_solib_dir,
+ rpath_roots,
+
+ # Outputs:
+ libraries_to_link,
+ expanded_linker_inputs,
+ library_search_directories,
+ rpath_roots_for_explicit_so_deps):
+ """Processes dynamic and interface libraries.
+
+ The LegacyLinkerInput is always expanded (added to expanded_linker_inputs).
+
+ When library is not in solib_dir or toolchain_libraries_solib_dir, a relative path from
+ the solib_dir to the library is added to `rpath_roots_for_explicit_so_deps` (for each rpath_root).
+
+ Path to the library is added to `library_search_directories`.
+
+ One of three flavours of LibraryToLinkValue are appended to libraries_to_link:
+ - for_dynamic_library
+ - for_versioned_dynamic_library
+ - for_interface_library
+
+ Args:
+ input: (LegacyLinkerInput) Linker input
+ feature_configuration: Feature configuration to be queried.
+ solib_dir: (str) solib directory.
+ toolchain_libraries_solib_dir: (list[str])
+ rpath_roots: (list[str]) rpath roots (for example solib_dir)
+ libraries_to_link: (list[LibraryToLinkValue]) Output collecting libraries to link.
+ expanded_linker_inputs: (list[LegacyLinkerInput]) Output collecting expanded linker inputs.
+ library_search_directories: (list[str]) Output collecting library search directories.
+ rpath_roots_for_explicit_so_deps: (list[str]) Output collecting rpaths.
+
+ Returns:
+ None
+ """
+ artifact_cat = input.artifact_category
+ if artifact_cat not in [artifact_category.DYNAMIC_LIBRARY, artifact_category.INTERFACE_LIBRARY]:
+ fail("Bad artifact category " + artifact_cat)
+
+ expanded_linker_inputs.append(input)
+
+ if (feature_configuration.is_enabled("targets_windows") and
+ feature_configuration.is_enabled("supports_interface_shared_libraries")):
+ # On Windows, dynamic library (dll) cannot be linked directly when using toolchains that
+ # support interface library (eg. MSVC). If the user is doing so, it is only to be referenced
+ # in other places (such as copy_dynamic_libraries_to_binary); skip adding it.
+ if is_shared_library(input):
+ return
+
+ input_file = input.file
+ lib_dir = input_file.dirname
+ if lib_dir != solib_dir and (not toolchain_libraries_solib_dir or toolchain_libraries_solib_dir != lib_dir):
+ # TODO(b/338618120): the code should be optimized to first get unique library_search_directories and
+ # then compute relative paths, i.e. rpath_roots_for_explicit_so_deps
+ # TODO(b/331164666): this is a duplication of _get_relative function implemented below
+ dotdots = ""
+ common_parent = solib_dir
+ for seg in reversed(common_parent.split("/")):
+ if paths.starts_with(lib_dir, common_parent):
+ break
+ dotdots += "../"
+ common_parent = common_parent[:-len(seg) - 1]
+
+ # When all dynamic deps are built in transitioned configurations, the default solib dir is
+ # not created. While resolving paths, the dynamic linker stops at the first directory that
+ # does not exist, even when followed by "../". We thus have to normalize the relative path.
+ for rpath_root in rpath_roots:
+ normalized_path_to_root = paths.normalize(rpath_root + dotdots + paths.relativize(lib_dir, common_parent))
+ rpath_roots_for_explicit_so_deps.append(normalized_path_to_root)
+
+ # Unless running locally, libraries will be available under the root relative path, so we
+ # should add that to the rpath as well.
+ if input_file.short_path.startswith("_solib_"):
+ artifact_path_under_solib = input_file.short_path.split("/")[1]
+ for rpath_root in rpath_roots:
+ rpath_roots_for_explicit_so_deps.append(
+ rpath_root + artifact_path_under_solib,
+ )
+
+ library_search_directories.append(lib_dir)
+
+ name = input_file.basename
+
+ # Use the normal shared library resolution rules if possible, otherwise treat as a versioned
+ # library that must use the exact name. e.g.:
+ # -lfoo -> libfoo.so
+ # -l:foo -> foo.so
+ # -l:libfoo.so.1 -> libfoo.so.1
+ has_compatible_name = (
+ name.startswith("lib") or
+ (not name.endswith(".so") and not name.endswith(".dylib") and not name.endswith(".dll"))
+ )
+ if is_shared_library(input_file) and has_compatible_name:
+ lib_name = name.removeprefix("lib").removesuffix(".so").removesuffix(".dylib").removesuffix(".dll")
+ libraries_to_link.append(cc_internal.for_dynamic_library(lib_name))
+ elif is_shared_library(input_file) or is_versioned_shared_library(input_file):
+ libraries_to_link.append(cc_internal.for_versioned_dynamic_library(name, input_file.path))
+ else:
+ # Interface shared objects have a non-standard extension
+ # that the linker won't be able to find. So use the
+ # filename directly rather than a -l option. Since the
+ # library has an SONAME attribute, this will work fine.
+ libraries_to_link.append(cc_internal.for_interface_library(input_file.path))
+
+def _add_static_input_link_options(
+ input,
+ feature_configuration,
+ use_start_end_lib,
+ need_whole_archive,
+ lto_map,
+ allow_lto_indexing,
+ # Outputs:
+ libraries_to_link,
+ expanded_linker_inputs):
+ """Processes static libraries and object files.
+
+ When start-end library is used, object files in static libraries are unpacked into following
+ flavours of LibraryToLinkValues:
+ - for_object_file
+ - for_object_file_group
+
+ Similarly object file inputs are repacked into the above two flavours, specially handled
+ object file tree artifacts.
+
+ When start-end library isn't used, static libraries are converted to for_static_library
+ LibraryToLinkValue.
+
+ Either whole library or library's object files LegacyLinkerInputs are expanded
+ (added to expanded_linker_inputs).
+
+ Args:
+ input: (LegacyLinkerInput) Linker input
+ feature_configuration: Feature configuration to be queried.
+ use_start_end_lib: (bool) Whether to use start end lib.
+ need_whole_archive: (bool) Whether we need to use whole-archive for the link.
+ lto_map: (dict[File, File]) Map from bitcode files to object files. Used to replace all linker inputs.
+ allow_lto_indexing: bool) Is LTO indexing being done.
+ libraries_to_link: (list[LibraryToLinkValue]) Output collecting libraries to link.
+ expanded_linker_inputs: (list[LegacyLinkerInput]) Output collecting expanded linker inputs.
+ """
+ artifact_cat = input.artifact_category
+ if artifact_cat not in [
+ artifact_category.OBJECT_FILE,
+ artifact_category.STATIC_LIBRARY,
+ artifact_category.ALWAYSLINK_STATIC_LIBRARY,
+ ]:
+ fail("Bad artifact category " + artifact_cat)
+
+ # input.disable_whole_archive should only be true for libstdc++/libc++ etc.
+ input_is_whole_archive = not input.disable_whole_archive and (
+ artifact_cat == artifact_category.ALWAYSLINK_STATIC_LIBRARY or need_whole_archive
+ )
+
+ if feature_configuration.is_enabled("use_lto_native_object_directory"):
+ shared_non_lto_obj_root_prefix = "shared.nonlto-obj"
+ else:
+ shared_non_lto_obj_root_prefix = "shared.nonlto"
+
+ # If we had any LTO artifacts, lto_map whould be non-null. In that case,
+ # we should have created a thinlto_param_file which the LTO indexing
+ # step will populate with the exec paths that correspond to the LTO
+ # artifacts that the linker decided to include based on symbol resolution.
+ # Those files will be included directly in the link (and not wrapped
+ # in --start-lib/--end-lib) to ensure consistency between the two link
+ # steps.
+
+ # start-lib/end-lib library: adds its input object files.
+ # TODO(bazel-team): Figure out if PicArchives are actually used. For it to be used, both
+ # linkingStatically and linkShared must me true, we must be in opt mode and cpu has to be k8.
+ if (use_start_end_lib and
+ artifact_cat in [artifact_category.STATIC_LIBRARY, artifact_category.ALWAYSLINK_STATIC_LIBRARY] and
+ input.object_files != None):
+ archive_members = input.object_files
+ non_lto_archive_members = []
+ for archive_member in archive_members:
+ # When ltoMap is non-empty the backend artifact may be missing due to libraries that
+ # list .o files explicitly, or generate .o files from assembler.
+ member = lto_map.pop(archive_member, archive_member)
+
+ # Object files are always (LTO or no LTO) expanded (input to the action).
+ expanded_linker_inputs.append(cc_internal.simple_linker_input(member))
+
+ if (member != archive_member and
+ _handled_by_lto_indexing(member, allow_lto_indexing, shared_non_lto_obj_root_prefix)):
+ # The LTO artifacts that should be included in the final link
+ # are listed in the thinltoParamFile, generated by the LTO indexing.
+ continue
+
+ # No LTO indexing step, so use the LTO backend's generated artifact directly
+ # instead of the bitcode object.
+ non_lto_archive_members.append(member)
+
+ if input_is_whole_archive:
+ for member in non_lto_archive_members:
+ if member.is_directory:
+ # TODO(b/78189629): This object filegroup is expanded at action time but wrapped
+ # with --start/--end-lib. There's currently no way to force these objects to be
+ # linked in.
+ libraries_to_link.append(cc_internal.for_object_file_group([member], is_whole_archive = True))
+ else:
+ # TODO(b/78189629): These each need to be their own LibraryToLinkValue so they're
+ # not wrapped in --start/--end-lib (which lets the linker leave out objects with
+ # unreferenced code).
+ libraries_to_link.append(cc_internal.for_object_file(member.path, is_whole_archive = True))
+ elif non_lto_archive_members:
+ libraries_to_link.append(cc_internal.for_object_file_group(non_lto_archive_members, is_whole_archive = False))
+ else:
+ input_file = lto_map.pop(input.file, input.file)
+ if (input_file != input.file and
+ _handled_by_lto_indexing(input_file, allow_lto_indexing, shared_non_lto_obj_root_prefix)):
+ # The LTO artifacts that should be included in the final link
+ # are listed in the thinltoParamFile, generated by the LTO indexing.
+
+ # Even if this object file is being skipped for exposure as a build variable, it's
+ # still an input to this action.
+ # TODO(b/331164666): simplify like in then branch above - expand the original input,
+ # instead creating a new one
+ expanded_linker_inputs.append(cc_internal.simple_linker_input(input_file))
+ return
+
+ # No LTO indexing step, so use the LTO backend's generated artifact directly
+ # instead of the bitcode object.
+ if artifact_cat == artifact_category.OBJECT_FILE:
+ if input.file.is_directory:
+ libraries_to_link.append(cc_internal.for_object_file_group([input_file], input_is_whole_archive))
+ else:
+ libraries_to_link.append(cc_internal.for_object_file(input_file.path, input_is_whole_archive))
+ if not input.is_linkstamp:
+ expanded_linker_inputs.append(input)
+ else:
+ libraries_to_link.append(cc_internal.for_static_library(input_file.path, input_is_whole_archive))
+ expanded_linker_inputs.append(input)
+
+def _handled_by_lto_indexing(file, allow_lto_indexing, shared_non_lto_obj_root_prefix):
+ """Returns true if this artifact is produced from a bitcode file.
+
+ Returns true if this artifact is produced from a bitcode file that will be input to the LTO
+ indexing step, in which case that step will add it to the generated thinlto_param_file for
+ inclusion in the final link step if the linker decides to include it.
+
+ Args:
+ file: (File) an artifact produced by an LTO backend.
+ allow_lto_indexing: (bool)
+ shared_non_lto_obj_root_prefix: (str) the root prefix of where the shared non lto obj are stored
+ """
+
+ # If no LTO indexing is allowed for this link, then none are handled by LTO indexing.
+ # Otherwise, this may be from a linkstatic library that we decided not to include in
+ # LTO indexing because we are linking a test, to improve scalability when linking many tests.
+ return allow_lto_indexing and not file.short_path.startswith(shared_non_lto_obj_root_prefix)
+
+def _collect_toolchain_runtime_library_search_directories(
+ cc_toolchain,
+ output,
+ potential_solib_parents,
+ need_toolchain_libraries_rpath,
+ toolchain_libraries_solib_dir,
+ is_native_deps,
+ workspace_name):
+ if not need_toolchain_libraries_rpath:
+ return depset()
+
+ runtime_library_search_directories = []
+ toolchain_libraries_solib_name = paths.basename(toolchain_libraries_solib_dir)
+ if not (is_native_deps and cc_toolchain.cc_configuration.share_native_deps):
+ for potential_exec_root in _find_toolchain_solib_parents(cc_toolchain, output, potential_solib_parents, toolchain_libraries_solib_dir, workspace_name):
+ runtime_library_search_directories.append(potential_exec_root + toolchain_libraries_solib_name + "/")
+
+ if is_native_deps:
+ runtime_library_search_directories.append("../" + toolchain_libraries_solib_name + "/")
+ runtime_library_search_directories.append(".")
+
+ runtime_library_search_directories.append(toolchain_libraries_solib_name + "/")
+
+ return depset(runtime_library_search_directories)
+
+# TODO(b/338618120): converge back together _find_toolchain_solib_parents and _find_potential_solib_parents
+
+def _find_potential_solib_parents(output, dynamic_library_solib_symlink_output, workspace_name):
+ solib_parents = []
+ outputs = [output]
+ if dynamic_library_solib_symlink_output:
+ outputs.append(dynamic_library_solib_symlink_output)
+
+ for output in outputs:
+ # The runtime location of the solib directory relative to the binary depends on four factors:
+ #
+ # * whether the binary is contained in the main repository or an external repository;
+ # * whether the binary is executed directly or from a runfiles tree;
+ # * whether the binary is staged as a symlink (sandboxed execution; local execution if the
+ # binary is in the runfiles of another target) or a regular file (remote execution) - the
+ # dynamic linker follows sandbox and runfiles symlinks into its location under the
+ # unsandboxed execroot, which thus becomes the effective $ORIGIN;
+ # * whether --experimental_sibling_repository_layout is enabled or not.
+ #
+ # The rpaths emitted into the binary thus have to cover the following cases (assuming that
+ # the binary target is located in the pkg `pkg` and has name `file`) for the directory used
+ # as $ORIGIN by the dynamic linker and the directory containing the solib directories:
+ #
+ # 1. main, direct, symlink:
+ # $ORIGIN: $EXECROOT/pkg
+ # solib root: $EXECROOT
+ # 2. main, direct, regular file:
+ # $ORIGIN: $EXECROOT/pkg
+ # solib root: $EXECROOT/pkg/file.runfiles/main_repo
+ # 3. main, runfiles, symlink:
+ # $ORIGIN: $EXECROOT/pkg
+ # solib root: $EXECROOT
+ # 4. main, runfiles, regular file:
+ # $ORIGIN: other_target.runfiles/main_repo/pkg
+ # solib root: other_target.runfiles/main_repo
+ # 5a. external, direct, symlink:
+ # $ORIGIN: $EXECROOT/external/other_repo/pkg
+ # solib root: $EXECROOT
+ # 5b. external, direct, symlink, with --experimental_sibling_repository_layout:
+ # $ORIGIN: $EXECROOT/../other_repo/pkg
+ # solib root: $EXECROOT/../other_repo
+ # 6a. external, direct, regular file:
+ # $ORIGIN: $EXECROOT/external/other_repo/pkg
+ # solib root: $EXECROOT/external/other_repo/pkg/file.runfiles/main_repo
+ # 6b. external, direct, regular file, with --experimental_sibling_repository_layout:
+ # $ORIGIN: $EXECROOT/../other_repo/pkg
+ # solib root: $EXECROOT/../other_repo/pkg/file.runfiles/other_repo
+ # 7a. external, runfiles, symlink:
+ # $ORIGIN: $EXECROOT/external/other_repo/pkg
+ # solib root: $EXECROOT
+ # 7b. external, runfiles, symlink, with --experimental_sibling_repository_layout:
+ # $ORIGIN: $EXECROOT/../other_repo/pkg
+ # solib root: $EXECROOT/../other_repo
+ # 8a. external, runfiles, regular file:
+ # $ORIGIN: other_target.runfiles/some_repo/pkg
+ # solib root: other_target.runfiles/main_repo
+ # 8b. external, runfiles, regular file, with --experimental_sibling_repository_layout:
+ # $ORIGIN: other_target.runfiles/some_repo/pkg
+ # solib root: other_target.runfiles/some_repo
+ #
+ # Cases 1, 3, 4, 5, 7, and 8b are covered by an rpath that walks up the root relative path.
+ # Cases 2 and 6 covered by walking into file.runfiles/main_repo.
+ # Case 8a is covered by walking up some_repo/pkg and then into main_repo.
+ is_external = output.short_path.startswith("../")
+ uses_legacy_repository_layout = output.short_path.startswith("../external")
+
+ # Handles cases 1, 3, 4, 5, and 7.
+ solib_parents.append("../" * (len(output.short_path.split("/")) - 1))
+
+ # Handle cases 2 and 6.
+ if is_external and not uses_legacy_repository_layout:
+ # Case 6b
+ solib_repository_name = output.short_path.split("/")[1]
+ else:
+ # Cases 2 and 6a
+ solib_repository_name = workspace_name
+
+ solib_parents.append(output.basename + ".runfiles/" + solib_repository_name + "/")
+ if is_external and uses_legacy_repository_layout:
+ # Handles case 8a. The runfiles path is of the form ../some_repo/pkg/file and we need to
+ # walk up some_repo/pkg and then down into main_repo.
+ solib_parents.append(
+ "../" * (len(output.root.path.split("/")) - 2) + workspace_name + "/",
+ )
+
+ return solib_parents
+
+def _find_toolchain_solib_parents(cc_toolchain, output, potential_solib_parents, toolchain_libraries_solib_dir, workspace_name):
+ uses_legacy_repository_layout = output.root.path.startswith("../external")
+
+ # When -experimental_sibling_repository_layout is not enabled, the toolchain solib sits next to
+ # the solib_<cpu> directory - so that it shares the same parents.
+ if uses_legacy_repository_layout:
+ return potential_solib_parents
+
+ # When -experimental_sibling_repository_layout is enabled, the toolchain solib is located in
+ # these 2 places:
+ # 1. The `bin` directory of the repository where the toolchain target is declared (this is the
+ # parent directory of `toolchainLibrariesSolibDir`).
+ # 2. In `target.runfiles/<toolchain repo>`
+ #
+ # And the following factors affect what $ORIGIN is resolved to:
+ # * whether the binary is contained in the main repository or an external repository;
+ # * whether the binary is executed directly or from a runfiles tree;
+ # * whether the binary is staged as a symlink (sandboxed execution; local execution if the
+ # binary is in the runfiles of another target) or a regular file (remote execution) - the
+ # dynamic linker follows sandbox and runfiles symlinks into its location under the
+ # unsandboxed execroot, which thus becomes the effective $ORIGIN;
+ #
+ # The rpaths emitted into the binary thus have to cover the following cases (assuming that
+ # the binary target is located in the pkg `pkg` and has name `file`) for the directory used
+ # as $ORIGIN by the dynamic linker and the directory containing the solib directories:
+ # 1. main, direct, symlink:
+ # $ORIGIN: $EXECROOT/pkg
+ # solib root: <toolchain repo bin>
+ # 2. main, direct, regular file:
+ # $ORIGIN: $EXECROOT/pkg
+ # solib root: $EXECROOT/pkg/file.runfiles/<toolchain repo>
+ # 3. main, runfiles, symlink:
+ # $ORIGIN: $EXECROOT/pkg
+ # solib root: <toolchain repo bin>
+ # 4. main, runfiles, regular file:
+ # $ORIGIN: other_target.runfiles/main_repo/pkg
+ # solib root: other_target.runfiles/<toolchain repo>
+ # 5. external, direct, symlink:
+ # $ORIGIN: $EXECROOT/../other_repo/pkg
+ # solib root: <toolchain repo bin>
+ # 6. external, direct, regular file:
+ # $ORIGIN: $EXECROOT/../other_repo/pkg
+ # solib root: $EXECROOT/../other_repo/pkg/file.runfiles/<toolchain repo>
+ # 7. external, runfiles, symlink:
+ # $ORIGIN: $EXECROOT/../other_repo/pkg
+ # solib root: <toolchain repo bin>
+ # 8. external, runfiles, regular file:
+ # $ORIGIN: other_target.runfiles/some_repo/pkg
+ # solib root: other_target.runfiles/<toolchain repo>
+ #
+ # For cases 1, 3, 5, 7, we need to compute the relative path from the output artifact to
+ # toolchain repo's bin directory. For 2 and 6, we walk down into `file.runfiles/<toolchain
+ # repo>`. For 4 and 8, we need to compute the relative path from the output runfile to
+ # <toolchain repo> under runfiles.
+ solib_parents = []
+
+ # Cases 1, 3, 5, 7
+ toolchain_bin_exec_path = paths.dirname(toolchain_libraries_solib_dir)
+ binary_origin_exec_path = output.dirname
+ solib_parents.append(
+ _get_relative(binary_origin_exec_path, toolchain_bin_exec_path) + "/",
+ )
+
+ # Cases 2 and 6
+ toolchain_runfiles_repo_name = _get_runfiles_repo_name(cc_toolchain._toolchain_label.repo_name, workspace_name)
+ solib_parents.append(
+ output.basename + ".runfiles/" + toolchain_runfiles_repo_name + "/",
+ )
+
+ # Cases 4 and 8
+ binary_repo_name = _get_runfiles_repo_name(output.owner.repo_name, workspace_name)
+ toolchain_bin_runfiles_path = toolchain_runfiles_repo_name
+ binary_origin_runfiles_path = binary_repo_name + "/" + output.dirname[len(output.root.path):].removeprefix("/")
+ solib_parents.append(
+ _get_relative(binary_origin_runfiles_path, toolchain_bin_runfiles_path) + "/",
+ )
+
+ return solib_parents
+
+def _get_runfiles_repo_name(repo_name, workspace_name):
+ # TODO(b/331164666): inline
+ return repo_name or workspace_name
+
+def _get_relative(start, to):
+ """
+ Returns the relative path from "from" to "to".
+
+ Example 1:
+ `_get_relative("foo", "foo/bar/wiz") -> returns "bar/wiz"`
+
+ Example 2:
+ `_get_relative("foo/bar/wiz", "foo/wiz") -> returns "../../wiz"`
+
+ The following requirements / assumptions are made: 1) paths must be both relative; 2) they
+ are assumed to be relative to the same location; 3) when the `from` path starts with
+ `..` prefixes, the prefix length must not exceed `..` prefixes of the `to` path.
+ """
+ common_parent = start
+ dotdots = ""
+ for seg in reversed(common_parent.split("/")):
+ if paths.starts_with(to, common_parent):
+ break
+ dotdots += "../"
+ common_parent = common_parent[:-len(seg) - 1]
+
+ return dotdots + paths.relativize(to, common_parent)