335 lines
12 KiB
Python
335 lines
12 KiB
Python
# Copyright 2022 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.
|
|
|
|
"""
|
|
Defines the native libs processing and an aspect to collect build configuration
|
|
of split deps
|
|
"""
|
|
|
|
load("//rules:common.bzl", "common")
|
|
|
|
SplitConfigInfo = provider(
|
|
doc = "Provides information about configuration for a split config dep",
|
|
fields = dict(
|
|
build_config = "The build configuration of the dep.",
|
|
),
|
|
)
|
|
|
|
def _split_config_aspect_impl(__, ctx):
|
|
return SplitConfigInfo(build_config = ctx.configuration)
|
|
|
|
split_config_aspect = aspect(
|
|
implementation = _split_config_aspect_impl,
|
|
)
|
|
|
|
def process(ctx, filename):
|
|
""" Links native deps into a shared library
|
|
|
|
Args:
|
|
ctx: The context.
|
|
filename: String. The name of the artifact containing the name of the
|
|
linked shared library
|
|
|
|
Returns:
|
|
Tuple of (libs, libs_name) where libs is a depset of all native deps
|
|
and libs_name is a File containing the basename of the linked shared
|
|
library
|
|
"""
|
|
actual_target_name = ctx.label.name.removesuffix(common.PACKAGED_RESOURCES_SUFFIX)
|
|
native_libs_basename = None
|
|
libs_name = None
|
|
libs = dict()
|
|
for key, deps in ctx.split_attr.deps.items():
|
|
cc_toolchain_dep = ctx.split_attr._cc_toolchain_split[key]
|
|
cc_toolchain = cc_toolchain_dep[cc_common.CcToolchainInfo]
|
|
build_config = cc_toolchain_dep[SplitConfigInfo].build_config
|
|
linker_input = cc_common.create_linker_input(
|
|
owner = ctx.label,
|
|
user_link_flags = ["-Wl,-soname=lib" + actual_target_name],
|
|
)
|
|
cc_info = cc_common.merge_cc_infos(
|
|
cc_infos = _concat(
|
|
[CcInfo(linking_context = cc_common.create_linking_context(
|
|
linker_inputs = depset([linker_input]),
|
|
))],
|
|
[dep[JavaInfo].cc_link_params_info for dep in deps if JavaInfo in dep],
|
|
[dep[AndroidCcLinkParamsInfo].link_params for dep in deps if AndroidCcLinkParamsInfo in dep],
|
|
[dep[CcInfo] for dep in deps if CcInfo in dep],
|
|
),
|
|
)
|
|
libraries = []
|
|
|
|
native_deps_lib = _link_native_deps_if_present(ctx, cc_info, cc_toolchain, build_config, actual_target_name)
|
|
if native_deps_lib:
|
|
libraries.append(native_deps_lib)
|
|
native_libs_basename = native_deps_lib.basename
|
|
|
|
libraries.extend(_filter_unique_shared_libs(native_deps_lib, cc_info))
|
|
|
|
if libraries:
|
|
libs[key] = depset(libraries)
|
|
|
|
if libs and native_libs_basename:
|
|
libs_name = ctx.actions.declare_file("nativedeps_filename/" + actual_target_name + "/" + filename)
|
|
ctx.actions.write(output = libs_name, content = native_libs_basename)
|
|
|
|
transitive_native_libs = _get_transitive_native_libs(ctx)
|
|
return AndroidBinaryNativeLibsInfo(libs, libs_name, transitive_native_libs)
|
|
|
|
# Collect all native shared libraries across split transitions. Some AARs
|
|
# contain shared libraries across multiple architectures, e.g. x86 and
|
|
# armeabi-v7a, and need to be packed into the APK.
|
|
def _get_transitive_native_libs(ctx):
|
|
return depset(
|
|
transitive = [
|
|
dep[AndroidNativeLibsInfo].native_libs
|
|
for dep in ctx.attr.deps
|
|
if AndroidNativeLibsInfo in dep
|
|
],
|
|
)
|
|
|
|
def _all_inputs(cc_info):
|
|
return [
|
|
lib
|
|
for input in cc_info.linking_context.linker_inputs.to_list()
|
|
for lib in input.libraries
|
|
]
|
|
|
|
def _filter_unique_shared_libs(linked_lib, cc_info):
|
|
basenames = {}
|
|
artifacts = {}
|
|
if linked_lib:
|
|
basenames[linked_lib.basename] = linked_lib
|
|
for input in _all_inputs(cc_info):
|
|
if input.pic_static_library or input.static_library:
|
|
# This is not a shared library and will not be loaded by Android, so skip it.
|
|
continue
|
|
|
|
artifact = None
|
|
if input.interface_library:
|
|
if input.resolved_symlink_interface_library:
|
|
artifact = input.resolved_symlink_interface_library
|
|
else:
|
|
artifact = input.interface_library
|
|
elif input.resolved_symlink_dynamic_library:
|
|
artifact = input.resolved_symlink_dynamic_library
|
|
else:
|
|
artifact = input.dynamic_library
|
|
|
|
if not artifact:
|
|
fail("Should never happen: did not find artifact for link!")
|
|
|
|
if artifact in artifacts:
|
|
# We have already reached this library, e.g., through a different solib symlink.
|
|
continue
|
|
artifacts[artifact] = None
|
|
basename = artifact.basename
|
|
if basename in basenames:
|
|
old_artifact = basenames[basename]
|
|
fail(
|
|
"Each library in the transitive closure must have a " +
|
|
"unique basename to avoid name collisions when packaged into " +
|
|
"an apk, but two libraries have the basename '" + basename +
|
|
"': " + artifact + " and " + old_artifact + (
|
|
" (the library compiled for this target)" if old_artifact == linked_lib else ""
|
|
),
|
|
)
|
|
else:
|
|
basenames[basename] = artifact
|
|
|
|
return artifacts.keys()
|
|
|
|
def _contains_code_to_link(input):
|
|
if not input.static_library and not input.pic_static_library:
|
|
# this is a shared library so we're going to have to copy it
|
|
return False
|
|
if input.objects:
|
|
object_files = input.objects
|
|
elif input.pic_objects:
|
|
object_files = input.pic_objects
|
|
elif _is_any_source_file(input.static_library, input.pic_static_library):
|
|
# this is an opaque library so we're going to have to link it
|
|
return True
|
|
else:
|
|
# if we reach here, this is a cc_library without sources generating an
|
|
# empty archive which does not need to be linked
|
|
# TODO(hvd): replace all such cc_library with exporting_cc_library
|
|
return False
|
|
for obj in object_files:
|
|
if not _is_shared_library(obj):
|
|
# this library was built with a non-shared-library object so we should link it
|
|
return True
|
|
return False
|
|
|
|
def _is_any_source_file(*files):
|
|
for file in files:
|
|
if file and file.is_source:
|
|
return True
|
|
return False
|
|
|
|
def _is_shared_library(lib_artifact):
|
|
if (lib_artifact.extension in ["so", "dll", "dylib"]):
|
|
return True
|
|
|
|
lib_name = lib_artifact.basename
|
|
|
|
# validate against the regex "^.+\\.((so)|(dylib))(\\.\\d\\w*)+$",
|
|
# must match VERSIONED_SHARED_LIBRARY.
|
|
for ext in (".so.", ".dylib."):
|
|
name, _, version = lib_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 _get_build_info(ctx):
|
|
return cc_common.get_build_info(ctx)
|
|
|
|
def _get_shared_native_deps_path(
|
|
linker_inputs,
|
|
link_opts,
|
|
linkstamps,
|
|
build_info_artifacts,
|
|
features,
|
|
is_test_target_partially_disabled_thin_lto):
|
|
fp = []
|
|
for artifact in linker_inputs:
|
|
fp.append(artifact.short_path)
|
|
fp.append(str(len(link_opts)))
|
|
for opt in link_opts:
|
|
fp.append(opt)
|
|
for artifact in linkstamps:
|
|
fp.append(artifact.short_path)
|
|
for artifact in build_info_artifacts:
|
|
fp.append(artifact.short_path)
|
|
for feature in features:
|
|
fp.append(feature)
|
|
|
|
fp.append("1" if is_test_target_partially_disabled_thin_lto else "0")
|
|
|
|
fingerprint = "%x" % hash("".join(fp))
|
|
return "_nativedeps/" + fingerprint
|
|
|
|
def _get_static_mode_params_for_dynamic_library_libraries(libs):
|
|
linker_inputs = []
|
|
for lib in libs:
|
|
if lib.pic_static_library:
|
|
linker_inputs.append(lib.pic_static_library)
|
|
elif lib.static_library:
|
|
linker_inputs.append(lib.static_library)
|
|
elif lib.interface_library:
|
|
linker_inputs.append(lib.interface_library)
|
|
else:
|
|
linker_inputs.append(lib.dynamic_library)
|
|
return linker_inputs
|
|
|
|
def _link_native_deps_if_present(ctx, cc_info, cc_toolchain, build_config, actual_target_name, is_test_rule_class = False):
|
|
needs_linking = False
|
|
for input in _all_inputs(cc_info):
|
|
needs_linking = needs_linking or _contains_code_to_link(input)
|
|
|
|
if not needs_linking:
|
|
return None
|
|
|
|
# This does not need to be shareable, but we use this API to specify the
|
|
# custom file root (matching the configuration)
|
|
output_lib = ctx.actions.declare_shareable_artifact(
|
|
ctx.label.package + "/nativedeps/" + actual_target_name + "/lib" + actual_target_name + ".so",
|
|
build_config.bin_dir,
|
|
)
|
|
|
|
link_opts = cc_info.linking_context.user_link_flags
|
|
|
|
linkstamps = []
|
|
for input in cc_info.linking_context.linker_inputs.to_list():
|
|
linkstamps.extend(input.linkstamps)
|
|
linkstamps_dict = {linkstamp: None for linkstamp in linkstamps}
|
|
|
|
build_info_artifacts = _get_build_info(ctx) if linkstamps_dict else []
|
|
requested_features = ["static_linking_mode", "native_deps_link"]
|
|
requested_features.extend(ctx.features)
|
|
if not "legacy_whole_archive" in ctx.disabled_features:
|
|
requested_features.append("legacy_whole_archive")
|
|
requested_features = sorted(requested_features)
|
|
feature_config = cc_common.configure_features(
|
|
ctx = ctx,
|
|
cc_toolchain = cc_toolchain,
|
|
requested_features = requested_features,
|
|
unsupported_features = ctx.disabled_features,
|
|
)
|
|
partially_disabled_thin_lto = (
|
|
cc_common.is_enabled(
|
|
feature_name = "thin_lto_linkstatic_tests_use_shared_nonlto_backends",
|
|
feature_configuration = feature_config,
|
|
) and not cc_common.is_enabled(
|
|
feature_name = "thin_lto_all_linkstatic_use_shared_nonlto_backends",
|
|
feature_configuration = feature_config,
|
|
)
|
|
)
|
|
test_only_target = ctx.attr.testonly or is_test_rule_class
|
|
share_native_deps = ctx.fragments.cpp.share_native_deps()
|
|
|
|
linker_inputs = _get_static_mode_params_for_dynamic_library_libraries(cc_info.linking_context.libraries_to_link)
|
|
|
|
if share_native_deps:
|
|
shared_path = _get_shared_native_deps_path(
|
|
linker_inputs,
|
|
link_opts,
|
|
[linkstamp.file() for linkstamp in linkstamps_dict],
|
|
build_info_artifacts,
|
|
requested_features,
|
|
test_only_target and partially_disabled_thin_lto,
|
|
)
|
|
linked_lib = ctx.actions.declare_shareable_artifact(shared_path + ".so", build_config.bin_dir)
|
|
else:
|
|
linked_lib = output_lib
|
|
|
|
cc_common.link(
|
|
name = ctx.label.name,
|
|
actions = ctx.actions,
|
|
linking_contexts = [cc_info.linking_context],
|
|
output_type = "dynamic_library",
|
|
never_link = True,
|
|
native_deps = True,
|
|
feature_configuration = feature_config,
|
|
cc_toolchain = cc_toolchain,
|
|
test_only_target = test_only_target,
|
|
stamp = ctx.attr.stamp,
|
|
grep_includes = ctx.file._grep_includes,
|
|
main_output = linked_lib,
|
|
use_shareable_artifact_factory = True,
|
|
build_config = build_config,
|
|
)
|
|
|
|
if (share_native_deps):
|
|
ctx.actions.symlink(
|
|
output = output_lib,
|
|
target_file = linked_lib,
|
|
)
|
|
return output_lib
|
|
else:
|
|
return linked_lib
|
|
|
|
def _concat(*list_of_lists):
|
|
res = []
|
|
for list in list_of_lists:
|
|
res.extend(list)
|
|
return res
|