1# Copyright 2022 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16Defines the native libs processing and an aspect to collect build configuration 17of split deps 18""" 19 20load("//rules:common.bzl", "common") 21 22SplitConfigInfo = provider( 23 doc = "Provides information about configuration for a split config dep", 24 fields = dict( 25 build_config = "The build configuration of the dep.", 26 ), 27) 28 29def _split_config_aspect_impl(__, ctx): 30 return SplitConfigInfo(build_config = ctx.configuration) 31 32split_config_aspect = aspect( 33 implementation = _split_config_aspect_impl, 34) 35 36def process(ctx, filename): 37 """ Links native deps into a shared library 38 39 Args: 40 ctx: The context. 41 filename: String. The name of the artifact containing the name of the 42 linked shared library 43 44 Returns: 45 Tuple of (libs, libs_name) where libs is a depset of all native deps 46 and libs_name is a File containing the basename of the linked shared 47 library 48 """ 49 actual_target_name = ctx.label.name.removesuffix(common.PACKAGED_RESOURCES_SUFFIX) 50 native_libs_basename = None 51 libs_name = None 52 libs = dict() 53 for key, deps in ctx.split_attr.deps.items(): 54 cc_toolchain_dep = ctx.split_attr._cc_toolchain_split[key] 55 cc_toolchain = cc_toolchain_dep[cc_common.CcToolchainInfo] 56 build_config = cc_toolchain_dep[SplitConfigInfo].build_config 57 linker_input = cc_common.create_linker_input( 58 owner = ctx.label, 59 user_link_flags = ["-Wl,-soname=lib" + actual_target_name], 60 ) 61 cc_info = cc_common.merge_cc_infos( 62 cc_infos = _concat( 63 [CcInfo(linking_context = cc_common.create_linking_context( 64 linker_inputs = depset([linker_input]), 65 ))], 66 [dep[JavaInfo].cc_link_params_info for dep in deps if JavaInfo in dep], 67 [dep[AndroidCcLinkParamsInfo].link_params for dep in deps if AndroidCcLinkParamsInfo in dep], 68 [dep[CcInfo] for dep in deps if CcInfo in dep], 69 ), 70 ) 71 libraries = [] 72 73 native_deps_lib = _link_native_deps_if_present(ctx, cc_info, cc_toolchain, build_config, actual_target_name) 74 if native_deps_lib: 75 libraries.append(native_deps_lib) 76 native_libs_basename = native_deps_lib.basename 77 78 libraries.extend(_filter_unique_shared_libs(native_deps_lib, cc_info)) 79 80 if libraries: 81 libs[key] = depset(libraries) 82 83 if libs and native_libs_basename: 84 libs_name = ctx.actions.declare_file("nativedeps_filename/" + actual_target_name + "/" + filename) 85 ctx.actions.write(output = libs_name, content = native_libs_basename) 86 87 transitive_native_libs = _get_transitive_native_libs(ctx) 88 return AndroidBinaryNativeLibsInfo(libs, libs_name, transitive_native_libs) 89 90# Collect all native shared libraries across split transitions. Some AARs 91# contain shared libraries across multiple architectures, e.g. x86 and 92# armeabi-v7a, and need to be packed into the APK. 93def _get_transitive_native_libs(ctx): 94 return depset( 95 transitive = [ 96 dep[AndroidNativeLibsInfo].native_libs 97 for dep in ctx.attr.deps 98 if AndroidNativeLibsInfo in dep 99 ], 100 ) 101 102def _all_inputs(cc_info): 103 return [ 104 lib 105 for input in cc_info.linking_context.linker_inputs.to_list() 106 for lib in input.libraries 107 ] 108 109def _filter_unique_shared_libs(linked_lib, cc_info): 110 basenames = {} 111 artifacts = {} 112 if linked_lib: 113 basenames[linked_lib.basename] = linked_lib 114 for input in _all_inputs(cc_info): 115 if input.pic_static_library or input.static_library: 116 # This is not a shared library and will not be loaded by Android, so skip it. 117 continue 118 119 artifact = None 120 if input.interface_library: 121 if input.resolved_symlink_interface_library: 122 artifact = input.resolved_symlink_interface_library 123 else: 124 artifact = input.interface_library 125 elif input.resolved_symlink_dynamic_library: 126 artifact = input.resolved_symlink_dynamic_library 127 else: 128 artifact = input.dynamic_library 129 130 if not artifact: 131 fail("Should never happen: did not find artifact for link!") 132 133 if artifact in artifacts: 134 # We have already reached this library, e.g., through a different solib symlink. 135 continue 136 artifacts[artifact] = None 137 basename = artifact.basename 138 if basename in basenames: 139 old_artifact = basenames[basename] 140 fail( 141 "Each library in the transitive closure must have a " + 142 "unique basename to avoid name collisions when packaged into " + 143 "an apk, but two libraries have the basename '" + basename + 144 "': " + artifact + " and " + old_artifact + ( 145 " (the library compiled for this target)" if old_artifact == linked_lib else "" 146 ), 147 ) 148 else: 149 basenames[basename] = artifact 150 151 return artifacts.keys() 152 153def _contains_code_to_link(input): 154 if not input.static_library and not input.pic_static_library: 155 # this is a shared library so we're going to have to copy it 156 return False 157 if input.objects: 158 object_files = input.objects 159 elif input.pic_objects: 160 object_files = input.pic_objects 161 elif _is_any_source_file(input.static_library, input.pic_static_library): 162 # this is an opaque library so we're going to have to link it 163 return True 164 else: 165 # if we reach here, this is a cc_library without sources generating an 166 # empty archive which does not need to be linked 167 # TODO(hvd): replace all such cc_library with exporting_cc_library 168 return False 169 for obj in object_files: 170 if not _is_shared_library(obj): 171 # this library was built with a non-shared-library object so we should link it 172 return True 173 return False 174 175def _is_any_source_file(*files): 176 for file in files: 177 if file and file.is_source: 178 return True 179 return False 180 181def _is_shared_library(lib_artifact): 182 if (lib_artifact.extension in ["so", "dll", "dylib"]): 183 return True 184 185 lib_name = lib_artifact.basename 186 187 # validate against the regex "^.+\\.((so)|(dylib))(\\.\\d\\w*)+$", 188 # must match VERSIONED_SHARED_LIBRARY. 189 for ext in (".so.", ".dylib."): 190 name, _, version = lib_name.rpartition(ext) 191 if name and version: 192 version_parts = version.split(".") 193 for part in version_parts: 194 if not part[0].isdigit(): 195 return False 196 for c in part[1:].elems(): 197 if not (c.isalnum() or c == "_"): 198 return False 199 return True 200 return False 201 202def _get_build_info(ctx): 203 return cc_common.get_build_info(ctx) 204 205def _get_shared_native_deps_path( 206 linker_inputs, 207 link_opts, 208 linkstamps, 209 build_info_artifacts, 210 features, 211 is_test_target_partially_disabled_thin_lto): 212 fp = [] 213 for artifact in linker_inputs: 214 fp.append(artifact.short_path) 215 fp.append(str(len(link_opts))) 216 for opt in link_opts: 217 fp.append(opt) 218 for artifact in linkstamps: 219 fp.append(artifact.short_path) 220 for artifact in build_info_artifacts: 221 fp.append(artifact.short_path) 222 for feature in features: 223 fp.append(feature) 224 225 fp.append("1" if is_test_target_partially_disabled_thin_lto else "0") 226 227 fingerprint = "%x" % hash("".join(fp)) 228 return "_nativedeps/" + fingerprint 229 230def _get_static_mode_params_for_dynamic_library_libraries(libs): 231 linker_inputs = [] 232 for lib in libs: 233 if lib.pic_static_library: 234 linker_inputs.append(lib.pic_static_library) 235 elif lib.static_library: 236 linker_inputs.append(lib.static_library) 237 elif lib.interface_library: 238 linker_inputs.append(lib.interface_library) 239 else: 240 linker_inputs.append(lib.dynamic_library) 241 return linker_inputs 242 243def _link_native_deps_if_present(ctx, cc_info, cc_toolchain, build_config, actual_target_name, is_test_rule_class = False): 244 needs_linking = False 245 for input in _all_inputs(cc_info): 246 needs_linking = needs_linking or _contains_code_to_link(input) 247 248 if not needs_linking: 249 return None 250 251 # This does not need to be shareable, but we use this API to specify the 252 # custom file root (matching the configuration) 253 output_lib = ctx.actions.declare_shareable_artifact( 254 ctx.label.package + "/nativedeps/" + actual_target_name + "/lib" + actual_target_name + ".so", 255 build_config.bin_dir, 256 ) 257 258 link_opts = cc_info.linking_context.user_link_flags 259 260 linkstamps = [] 261 for input in cc_info.linking_context.linker_inputs.to_list(): 262 linkstamps.extend(input.linkstamps) 263 linkstamps_dict = {linkstamp: None for linkstamp in linkstamps} 264 265 build_info_artifacts = _get_build_info(ctx) if linkstamps_dict else [] 266 requested_features = ["static_linking_mode", "native_deps_link"] 267 requested_features.extend(ctx.features) 268 if not "legacy_whole_archive" in ctx.disabled_features: 269 requested_features.append("legacy_whole_archive") 270 requested_features = sorted(requested_features) 271 feature_config = cc_common.configure_features( 272 ctx = ctx, 273 cc_toolchain = cc_toolchain, 274 requested_features = requested_features, 275 unsupported_features = ctx.disabled_features, 276 ) 277 partially_disabled_thin_lto = ( 278 cc_common.is_enabled( 279 feature_name = "thin_lto_linkstatic_tests_use_shared_nonlto_backends", 280 feature_configuration = feature_config, 281 ) and not cc_common.is_enabled( 282 feature_name = "thin_lto_all_linkstatic_use_shared_nonlto_backends", 283 feature_configuration = feature_config, 284 ) 285 ) 286 test_only_target = ctx.attr.testonly or is_test_rule_class 287 share_native_deps = ctx.fragments.cpp.share_native_deps() 288 289 linker_inputs = _get_static_mode_params_for_dynamic_library_libraries(cc_info.linking_context.libraries_to_link) 290 291 if share_native_deps: 292 shared_path = _get_shared_native_deps_path( 293 linker_inputs, 294 link_opts, 295 [linkstamp.file() for linkstamp in linkstamps_dict], 296 build_info_artifacts, 297 requested_features, 298 test_only_target and partially_disabled_thin_lto, 299 ) 300 linked_lib = ctx.actions.declare_shareable_artifact(shared_path + ".so", build_config.bin_dir) 301 else: 302 linked_lib = output_lib 303 304 cc_common.link( 305 name = ctx.label.name, 306 actions = ctx.actions, 307 linking_contexts = [cc_info.linking_context], 308 output_type = "dynamic_library", 309 never_link = True, 310 native_deps = True, 311 feature_configuration = feature_config, 312 cc_toolchain = cc_toolchain, 313 test_only_target = test_only_target, 314 stamp = ctx.attr.stamp, 315 grep_includes = ctx.file._grep_includes, 316 main_output = linked_lib, 317 use_shareable_artifact_factory = True, 318 build_config = build_config, 319 ) 320 321 if (share_native_deps): 322 ctx.actions.symlink( 323 output = output_lib, 324 target_file = linked_lib, 325 ) 326 return output_lib 327 else: 328 return linked_lib 329 330def _concat(*list_of_lists): 331 res = [] 332 for list in list_of_lists: 333 res.extend(list) 334 return res 335