1"""This is an experimental implementation of cc_shared_library. 2 3We may change the implementation at any moment or even delete this file. Do not 4rely on this. It requires bazel >1.2 and passing the flag 5--experimental_cc_shared_library 6""" 7 8load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") 9load("//cc:find_cc_toolchain.bzl", "find_cc_toolchain") 10 11# TODO(#5200): Add export_define to library_to_link and cc_library 12 13# Add this as a tag to any target that can be linked by more than one 14# cc_shared_library because it doesn't have static initializers or anything 15# else that may cause issues when being linked more than once. This should be 16# used sparingly after making sure it's safe to use. 17LINKABLE_MORE_THAN_ONCE = "LINKABLE_MORE_THAN_ONCE" 18 19CcSharedLibraryPermissionsInfo = provider( 20 "Permissions for a cc shared library.", 21 fields = { 22 "targets": "Matches targets that can be exported.", 23 }, 24) 25GraphNodeInfo = provider( 26 "Nodes in the graph of shared libraries.", 27 fields = { 28 "children": "Other GraphNodeInfo from dependencies of this target", 29 "label": "Label of the target visited", 30 "linkable_more_than_once": "Linkable into more than a single cc_shared_library", 31 }, 32) 33CcSharedLibraryInfo = provider( 34 "Information about a cc shared library.", 35 fields = { 36 "dynamic_deps": "All shared libraries depended on transitively", 37 "exports": "cc_libraries that are linked statically and exported", 38 "link_once_static_libs": "All libraries linked statically into this library that should " + 39 "only be linked once, e.g. because they have static " + 40 "initializers. If we try to link them more than once, " + 41 "we will throw an error", 42 "linker_input": "the resulting linker input artifact for the shared library", 43 "preloaded_deps": "cc_libraries needed by this cc_shared_library that should" + 44 " be linked the binary. If this is set, this cc_shared_library has to " + 45 " be a direct dependency of the cc_binary", 46 }, 47) 48 49def _separate_static_and_dynamic_link_libraries( 50 direct_children, 51 can_be_linked_dynamically, 52 preloaded_deps_direct_labels): 53 node = None 54 all_children = list(direct_children) 55 link_statically_labels = {} 56 link_dynamically_labels = {} 57 58 seen_labels = {} 59 60 # Horrible I know. Perhaps Starlark team gives me a way to prune a tree. 61 for i in range(2147483647): 62 if i == len(all_children): 63 break 64 65 node = all_children[i] 66 node_label = str(node.label) 67 68 if node_label in seen_labels: 69 continue 70 seen_labels[node_label] = True 71 72 if node_label in can_be_linked_dynamically: 73 link_dynamically_labels[node_label] = True 74 elif node_label not in preloaded_deps_direct_labels: 75 link_statically_labels[node_label] = node.linkable_more_than_once 76 all_children.extend(node.children) 77 78 return (link_statically_labels, link_dynamically_labels) 79 80def _create_linker_context(ctx, linker_inputs): 81 return cc_common.create_linking_context( 82 linker_inputs = depset(linker_inputs, order = "topological"), 83 ) 84 85def _merge_cc_shared_library_infos(ctx): 86 dynamic_deps = [] 87 transitive_dynamic_deps = [] 88 for dep in ctx.attr.dynamic_deps: 89 if dep[CcSharedLibraryInfo].preloaded_deps != None: 90 fail("{} can only be a direct dependency of a " + 91 " cc_binary because it has " + 92 "preloaded_deps".format(str(dep.label))) 93 dynamic_dep_entry = ( 94 dep[CcSharedLibraryInfo].exports, 95 dep[CcSharedLibraryInfo].linker_input, 96 dep[CcSharedLibraryInfo].link_once_static_libs, 97 ) 98 dynamic_deps.append(dynamic_dep_entry) 99 transitive_dynamic_deps.append(dep[CcSharedLibraryInfo].dynamic_deps) 100 101 return depset(direct = dynamic_deps, transitive = transitive_dynamic_deps) 102 103def _build_exports_map_from_only_dynamic_deps(merged_shared_library_infos): 104 exports_map = {} 105 for entry in merged_shared_library_infos.to_list(): 106 exports = entry[0] 107 linker_input = entry[1] 108 for export in exports: 109 if export in exports_map: 110 fail("Two shared libraries in dependencies export the same symbols. Both " + 111 exports_map[export].libraries[0].dynamic_library.short_path + 112 " and " + linker_input.libraries[0].dynamic_library.short_path + 113 " export " + export) 114 exports_map[export] = linker_input 115 return exports_map 116 117def _build_link_once_static_libs_map(merged_shared_library_infos): 118 link_once_static_libs_map = {} 119 for entry in merged_shared_library_infos.to_list(): 120 link_once_static_libs = entry[2] 121 linker_input = entry[1] 122 for static_lib in link_once_static_libs: 123 if static_lib in link_once_static_libs_map: 124 fail("Two shared libraries in dependencies link the same " + 125 " library statically. Both " + link_once_static_libs_map[static_lib] + 126 " and " + str(linker_input.owner) + 127 " link statically" + static_lib) 128 link_once_static_libs_map[static_lib] = str(linker_input.owner) 129 return link_once_static_libs_map 130 131def _wrap_static_library_with_alwayslink(ctx, feature_configuration, cc_toolchain, linker_input): 132 new_libraries_to_link = [] 133 for old_library_to_link in linker_input.libraries: 134 # TODO(#5200): This will lose the object files from a library to link. 135 # Not too bad for the prototype but as soon as the library_to_link 136 # constructor has object parameters this should be changed. 137 new_library_to_link = cc_common.create_library_to_link( 138 actions = ctx.actions, 139 feature_configuration = feature_configuration, 140 cc_toolchain = cc_toolchain, 141 static_library = old_library_to_link.static_library, 142 pic_static_library = old_library_to_link.pic_static_library, 143 alwayslink = True, 144 ) 145 new_libraries_to_link.append(new_library_to_link) 146 147 return cc_common.create_linker_input( 148 owner = linker_input.owner, 149 libraries = depset(direct = new_libraries_to_link), 150 user_link_flags = depset(direct = linker_input.user_link_flags), 151 additional_inputs = depset(direct = linker_input.additional_inputs), 152 ) 153 154def _check_if_target_under_path(value, pattern): 155 if pattern.workspace_name != value.workspace_name: 156 return False 157 if pattern.name == "__pkg__": 158 return pattern.package == value.package 159 if pattern.name == "__subpackages__": 160 return _same_package_or_above(pattern, value) 161 162 return pattern.package == value.package and pattern.name == value.name 163 164def _check_if_target_can_be_exported(target, current_label, permissions): 165 if permissions == None: 166 return True 167 168 if (target.workspace_name != current_label.workspace_name or 169 _same_package_or_above(current_label, target)): 170 return True 171 172 matched_by_target = False 173 for permission in permissions: 174 for permission_target in permission[CcSharedLibraryPermissionsInfo].targets: 175 if _check_if_target_under_path(target, permission_target): 176 return True 177 178 return False 179 180def _check_if_target_should_be_exported_without_filter(target, current_label, permissions): 181 return _check_if_target_should_be_exported_with_filter(target, current_label, None, permissions) 182 183def _check_if_target_should_be_exported_with_filter(target, current_label, exports_filter, permissions): 184 should_be_exported = False 185 if exports_filter == None: 186 should_be_exported = True 187 else: 188 for export_filter in exports_filter: 189 export_filter_label = current_label.relative(export_filter) 190 if _check_if_target_under_path(target, export_filter_label): 191 should_be_exported = True 192 break 193 194 if should_be_exported: 195 if _check_if_target_can_be_exported(target, current_label, permissions): 196 return True 197 else: 198 matched_by_filter_text = "" 199 if exports_filter: 200 matched_by_filter_text = " (matched by filter) " 201 fail(str(target) + matched_by_filter_text + 202 " cannot be exported from " + str(current_label) + 203 " because it's not in the same package/subpackage and the library " + 204 "doesn't have the necessary permissions. Use cc_shared_library_permissions.") 205 206 return False 207 208def _filter_inputs( 209 ctx, 210 feature_configuration, 211 cc_toolchain, 212 transitive_exports, 213 preloaded_deps_direct_labels, 214 link_once_static_libs_map): 215 linker_inputs = [] 216 link_once_static_libs = [] 217 218 graph_structure_aspect_nodes = [] 219 dependency_linker_inputs = [] 220 direct_exports = {} 221 for export in ctx.attr.roots: 222 direct_exports[str(export.label)] = True 223 dependency_linker_inputs.extend(export[CcInfo].linking_context.linker_inputs.to_list()) 224 graph_structure_aspect_nodes.append(export[GraphNodeInfo]) 225 226 can_be_linked_dynamically = {} 227 for linker_input in dependency_linker_inputs: 228 owner = str(linker_input.owner) 229 if owner in transitive_exports: 230 can_be_linked_dynamically[owner] = True 231 232 (link_statically_labels, link_dynamically_labels) = _separate_static_and_dynamic_link_libraries( 233 graph_structure_aspect_nodes, 234 can_be_linked_dynamically, 235 preloaded_deps_direct_labels, 236 ) 237 238 exports = {} 239 owners_seen = {} 240 for linker_input in dependency_linker_inputs: 241 owner = str(linker_input.owner) 242 if owner in owners_seen: 243 continue 244 owners_seen[owner] = True 245 if owner in link_dynamically_labels: 246 dynamic_linker_input = transitive_exports[owner] 247 linker_inputs.append(dynamic_linker_input) 248 elif owner in link_statically_labels: 249 if owner in link_once_static_libs_map: 250 fail(owner + " is already linked statically in " + 251 link_once_static_libs_map[owner] + " but not exported") 252 253 if owner in direct_exports: 254 wrapped_library = _wrap_static_library_with_alwayslink( 255 ctx, 256 feature_configuration, 257 cc_toolchain, 258 linker_input, 259 ) 260 261 if not link_statically_labels[owner]: 262 link_once_static_libs.append(owner) 263 linker_inputs.append(wrapped_library) 264 else: 265 can_be_linked_statically = False 266 267 for static_dep_path in ctx.attr.static_deps: 268 static_dep_path_label = ctx.label.relative(static_dep_path) 269 if _check_if_target_under_path(linker_input.owner, static_dep_path_label): 270 can_be_linked_statically = True 271 break 272 273 if _check_if_target_should_be_exported_with_filter( 274 linker_input.owner, 275 ctx.label, 276 ctx.attr.exports_filter, 277 _get_permissions(ctx), 278 ): 279 exports[owner] = True 280 can_be_linked_statically = True 281 282 if can_be_linked_statically: 283 if not link_statically_labels[owner]: 284 link_once_static_libs.append(owner) 285 linker_inputs.append(linker_input) 286 else: 287 fail("We can't link " + 288 str(owner) + " either statically or dynamically") 289 290 return (exports, linker_inputs, link_once_static_libs) 291 292def _same_package_or_above(label_a, label_b): 293 if label_a.workspace_name != label_b.workspace_name: 294 return False 295 package_a_tokenized = label_a.package.split("/") 296 package_b_tokenized = label_b.package.split("/") 297 if len(package_b_tokenized) < len(package_a_tokenized): 298 return False 299 300 if package_a_tokenized[0] != "": 301 for i in range(len(package_a_tokenized)): 302 if package_a_tokenized[i] != package_b_tokenized[i]: 303 return False 304 305 return True 306 307def _get_permissions(ctx): 308 if ctx.attr._enable_permissions_check[BuildSettingInfo].value: 309 return ctx.attr.permissions 310 return None 311 312def _process_version_script(ctx): 313 if ctx.attr.version_script == None: 314 return ([], []) 315 316 version_script = ctx.files.version_script[0] 317 version_script_arg = "-Wl,--version-script," + version_script.path 318 return ([version_script], [version_script_arg]) 319 320def _cc_shared_library_impl(ctx): 321 cc_common.check_experimental_cc_shared_library() 322 cc_toolchain = find_cc_toolchain(ctx) 323 feature_configuration = cc_common.configure_features( 324 ctx = ctx, 325 cc_toolchain = cc_toolchain, 326 requested_features = ctx.features, 327 unsupported_features = ctx.disabled_features, 328 ) 329 330 merged_cc_shared_library_info = _merge_cc_shared_library_infos(ctx) 331 exports_map = _build_exports_map_from_only_dynamic_deps(merged_cc_shared_library_info) 332 for export in ctx.attr.roots: 333 if str(export.label) in exports_map: 334 fail("Trying to export a library already exported by a different shared library: " + 335 str(export.label)) 336 337 _check_if_target_should_be_exported_without_filter(export.label, ctx.label, _get_permissions(ctx)) 338 339 preloaded_deps_direct_labels = {} 340 preloaded_dep_merged_cc_info = None 341 if len(ctx.attr.preloaded_deps) != 0: 342 preloaded_deps_cc_infos = [] 343 for preloaded_dep in ctx.attr.preloaded_deps: 344 preloaded_deps_direct_labels[str(preloaded_dep.label)] = True 345 preloaded_deps_cc_infos.append(preloaded_dep[CcInfo]) 346 347 preloaded_dep_merged_cc_info = cc_common.merge_cc_infos(cc_infos = preloaded_deps_cc_infos) 348 349 link_once_static_libs_map = _build_link_once_static_libs_map(merged_cc_shared_library_info) 350 351 (exports, linker_inputs, link_once_static_libs) = _filter_inputs( 352 ctx, 353 feature_configuration, 354 cc_toolchain, 355 exports_map, 356 preloaded_deps_direct_labels, 357 link_once_static_libs_map, 358 ) 359 360 linking_context = _create_linker_context(ctx, linker_inputs) 361 362 # Divergence from rules_cc: that version does not support version scripts 363 version_script, version_script_arg = _process_version_script(ctx) 364 365 user_link_flags = version_script_arg[:] 366 for user_link_flag in ctx.attr.user_link_flags: 367 user_link_flags.append(ctx.expand_location(user_link_flag, targets = ctx.attr.additional_linker_inputs)) 368 369 linking_outputs = cc_common.link( 370 actions = ctx.actions, 371 feature_configuration = feature_configuration, 372 cc_toolchain = cc_toolchain, 373 linking_contexts = [linking_context], 374 user_link_flags = user_link_flags, 375 additional_inputs = ctx.files.additional_linker_inputs + version_script, 376 name = ctx.label.name, 377 output_type = "dynamic_library", 378 ) 379 380 runfiles = ctx.runfiles( 381 files = [linking_outputs.library_to_link.resolved_symlink_dynamic_library], 382 ) 383 for dep in ctx.attr.dynamic_deps: 384 runfiles = runfiles.merge(dep[DefaultInfo].data_runfiles) 385 386 for export in ctx.attr.roots: 387 exports[str(export.label)] = True 388 389 debug_files = [] 390 if ctx.attr._experimental_debug[BuildSettingInfo].value: 391 exports_debug_file = ctx.actions.declare_file(ctx.label.name + "_exports.txt") 392 ctx.actions.write(content = "\n".join(exports.keys()), output = exports_debug_file) 393 394 link_once_static_libs_debug_file = ctx.actions.declare_file(ctx.label.name + "_link_once_static_libs.txt") 395 ctx.actions.write(content = "\n".join(link_once_static_libs), output = link_once_static_libs_debug_file) 396 397 debug_files.append(exports_debug_file) 398 debug_files.append(link_once_static_libs_debug_file) 399 400 if not ctx.attr._incompatible_link_once[BuildSettingInfo].value: 401 link_once_static_libs = [] 402 403 return [ 404 DefaultInfo( 405 files = depset([linking_outputs.library_to_link.resolved_symlink_dynamic_library] + debug_files), 406 runfiles = runfiles, 407 ), 408 CcSharedLibraryInfo( 409 dynamic_deps = merged_cc_shared_library_info, 410 exports = exports.keys(), 411 link_once_static_libs = link_once_static_libs, 412 linker_input = cc_common.create_linker_input( 413 owner = ctx.label, 414 libraries = depset([linking_outputs.library_to_link]), 415 ), 416 preloaded_deps = preloaded_dep_merged_cc_info, 417 ), 418 ] 419 420def _graph_structure_aspect_impl(target, ctx): 421 children = [] 422 423 if hasattr(ctx.rule.attr, "deps"): 424 for dep in ctx.rule.attr.deps: 425 if GraphNodeInfo in dep: 426 children.append(dep[GraphNodeInfo]) 427 428 # TODO(bazel-team): Add flag to Bazel that can toggle the initialization of 429 # linkable_more_than_once. 430 linkable_more_than_once = False 431 if hasattr(ctx.rule.attr, "tags"): 432 for tag in ctx.rule.attr.tags: 433 if tag == LINKABLE_MORE_THAN_ONCE: 434 linkable_more_than_once = True 435 436 return [GraphNodeInfo( 437 label = ctx.label, 438 children = children, 439 linkable_more_than_once = linkable_more_than_once, 440 )] 441 442def _cc_shared_library_permissions_impl(ctx): 443 targets = [] 444 for target_filter in ctx.attr.targets: 445 target_filter_label = ctx.label.relative(target_filter) 446 if not _check_if_target_under_path(target_filter_label, ctx.label.relative(":__subpackages__")): 447 fail("A cc_shared_library_permissions rule can only list " + 448 "targets that are in the same package or a sub-package") 449 targets.append(target_filter_label) 450 451 return [CcSharedLibraryPermissionsInfo( 452 targets = targets, 453 )] 454 455graph_structure_aspect = aspect( 456 attr_aspects = ["*"], 457 implementation = _graph_structure_aspect_impl, 458) 459 460cc_shared_library_permissions = rule( 461 implementation = _cc_shared_library_permissions_impl, 462 attrs = { 463 "targets": attr.string_list(), 464 }, 465) 466 467cc_shared_library = rule( 468 implementation = _cc_shared_library_impl, 469 attrs = { 470 "additional_linker_inputs": attr.label_list(allow_files = True), 471 "dynamic_deps": attr.label_list(providers = [CcSharedLibraryInfo]), 472 "exports_filter": attr.string_list(), 473 "permissions": attr.label_list(providers = [CcSharedLibraryPermissionsInfo]), 474 "preloaded_deps": attr.label_list(providers = [CcInfo]), 475 "roots": attr.label_list(providers = [CcInfo], aspects = [graph_structure_aspect]), 476 "static_deps": attr.string_list(), 477 "version_script": attr.label(allow_single_file = True), 478 "user_link_flags": attr.string_list(), 479 "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"), 480 "_enable_permissions_check": attr.label(default = "//examples:enable_permissions_check"), 481 "_experimental_debug": attr.label(default = "//examples:experimental_debug"), 482 "_incompatible_link_once": attr.label(default = "//examples:incompatible_link_once"), 483 }, 484 toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], # copybara-use-repo-external-label 485 fragments = ["cpp"], 486 incompatible_use_toolchain_transition = True, 487) 488 489for_testing_dont_use_check_if_target_under_path = _check_if_target_under_path 490