blob: f97afee5d9ca86a9ff37c8b4f4c897e91731d81b [file]
# Copyright 2025 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.
"""
Extra link-time library functionality.
CcInfo maintains a list of extra libraries to include in a link. These are non-C++ libraries that
are built from inputs gathered from all the dependencies. The dependencies have no way to
coordinate, so each one will add an ExtraLinkTimeLibrary to its CcLinkingContextInfo.
"""
load("//cc/common:cc_helper_internal.bzl", "check_private_api")
load("//cc/private:cc_internal.bzl", _cc_internal = "cc_internal")
# An implementation of ExtraLinkTimeLibrary that uses functions and data passed in from Starlark.
# Fields are intentionally not specified as they may be defined by users.
# Common fields are:
# "build_library_func": "Starlark function to create the output library.",
# "_key": """Key object used to determine the "class" of the library implementation.
# The equals method is used to determine equality.""",
# User defined fields may be anything. Depsets are be combined when merging libraries.
# buildifier: disable=provider-params
ExtraLinkTimeLibraryInfo = provider("ExtraLinkTimeLibraryInfo")
# Fields are intentionally not specified as they may be defined by users.
# Common fields are:
# "transitive_linker_inputs": "depset[LinkerInput]. Linker inputs created.",
# "transitive_runtime_libraries": "depset[File]. Runtime libraries created.",
# buildifier: disable=provider-params
ExtraLibraryInfo = provider("The result of building extra link-time libraries.")
_KeyInfo = provider("_KeyInfo", fields = ["build_library_func", "constant_fields", "depset_fields"])
def create_extra_link_time_library(*, build_library_func, **kwargs):
"""An extra library to include in a link. The actual library is built at link time.
Exposed as cc_common.create_extra_link_time_library.
This can be used for non-C++ inputs to a C++ link. A class that implements this interface will
support transitively gathering all inputs from link dependencies, and then combine them all
ogether into a set of C++ libraries.
Any implementations must be immutable (and therefore thread-safe), because this is passed
between rules and accessed in a multi-threaded context.
Args:
build_library_func: A function that takes a rule context, static_mode, for_dynamic_library,
and kwargs, and returns a tuple of (linker_input, runtime_library): `tuple[LinkerInputInfo,
File]`.
**kwargs: Additional fields to pass to the build function.
Returns:
ExtraLinkTimeLibraryInfo.
"""
_cc_internal.check_toplevel(build_library_func)
return ExtraLinkTimeLibraryInfo(
build_library_func = build_library_func,
# Key to identify the "class" of a StarlarkDefinedLinkTimeLibrary. Uses the build function and
# the split between depset and non-depset parameters to determine equality.
_key = _KeyInfo(
build_library_func = build_library_func,
# _KeyInfo is used in a dict, so all of its fields must be frozen/hashable.
constant_fields = _cc_internal.freeze([k for k, v in kwargs.items() if type(v) != "depset"]),
depset_fields = _cc_internal.freeze([k for k, v in kwargs.items() if type(v) == "depset"]),
),
**kwargs
)
ExtraLinkTimeLibrariesInfo = provider(
"ExtraLinkTimeLibrariesInfo",
fields = {
"libraries": "A list of (ExtraLinkTimeLibraryInfo) extra libraries.",
},
)
_EMPTY = ExtraLinkTimeLibrariesInfo(
libraries = [],
)
def create_extra_link_time_libraries(library):
"""Creates ExtraLinkTimeLibrariesInfo.
Args:
library: A single ExtraLinkTimeLibraryInfo (or None).
Returns:
ExtraLinkTimeLibrariesInfo.
"""
if library == None:
return _EMPTY
libraries = _cc_internal.freeze([library])
return ExtraLinkTimeLibrariesInfo(
libraries = libraries,
)
def _merge_values(values):
if not values:
return None
first_value = values[0]
if type(first_value) == "depset":
return depset(transitive = values, order = "topological")
else:
# All the constant values should be the same,
# but this wasn't always enforced and they aren't :(.
return first_value
def merge_extra_link_time_libraries(libraries):
"""Merges a list of ExtraLinkTimeLibraryInfos.
Args:
libraries: A list of ExtraLinkTimeLibraryInfos.
Returns:
ExtraLinkTimeLibrariesInfo.
"""
if not libraries:
return _EMPTY
merged_libraries = {}
for extra_library in libraries:
for library in extra_library.libraries:
key = library._key
if key not in merged_libraries:
merged_libraries[key] = []
merged_libraries[key].append(library)
result = []
for key, libs_to_merge in merged_libraries.items():
if len(libs_to_merge) == 1:
result.append(libs_to_merge[0])
continue
merged_fields = {"build_library_func": key.build_library_func, "_key": key}
all_keys = key.constant_fields + key.depset_fields
for field in all_keys:
merged_fields[field] = _merge_values([getattr(lib, field) for lib in libs_to_merge])
result.append(ExtraLinkTimeLibraryInfo(**merged_fields))
return ExtraLinkTimeLibrariesInfo(
libraries = _cc_internal.freeze(result),
)
def build_libraries(extra_libraries, ctx, static_mode, for_dynamic_library):
"""Builds the extra link-time libraries.
Args:
extra_libraries: A list of ExtraLinkTimeLibraryInfo objects.
ctx: The rule context.
static_mode: Whether the link is static.
for_dynamic_library: Whether the link is for a dynamic library.
Returns:
ExtraLibraryInfo.
"""
check_private_api()
transitive_linker_inputs = []
transitive_runtime_libraries = []
additional_stamp_infos = []
for library in extra_libraries:
kwargs = {}
for key in dir(library):
if key not in ["build_library_func", "_key"]:
kwargs[key] = getattr(library, key)
ret = library.build_library_func(
ctx,
static_mode,
for_dynamic_library,
**kwargs
)
transitive_linker_inputs.append(ret.linker_input)
transitive_runtime_libraries.append(ret.runtime_library)
if hasattr(ret, "additional_stamp_info"):
additional_stamp_infos.append(ret.additional_stamp_info)
return ExtraLibraryInfo(
transitive_linker_inputs = depset(transitive = transitive_linker_inputs),
transitive_runtime_libraries = depset(transitive = transitive_runtime_libraries),
additional_stamp_infos = additional_stamp_infos,
)