Skip to content

Commit

Permalink
Add NO_EXPORTING tag to indicate that a lib doesn't export any
Browse files Browse the repository at this point in the history
symbols therefore it is safe to link it more than once.

This is useful in some cases, for example,
a static lib has a constructor that needs to be run during loading time of the
shared lib that has it linked into, and this is how the code gets called by the
OS. This static lib might need to be linked as a whole archive dep for multiple
shared libs, otherwise this static lib will be dropped by the linker since
there are no incoming symbol references.

RELNOTES:none
PiperOrigin-RevId: 496497650
Change-Id: I9c13408c051320321fabcb3863d87e76c53b4568
  • Loading branch information
Googler authored and copybara-github committed Dec 19, 2022
1 parent 961158b commit 5ba2d25
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ cc_common = _builtins.toplevel.cc_common
# used sparingly after making sure it's safe to use.
LINKABLE_MORE_THAN_ONCE = "LINKABLE_MORE_THAN_ONCE"

# Add this as a tag to any static lib target that doesn't export any symbols,
# thus can be statically linked more than once. This is useful in some cases,
# for example, a static lib has a constructor that needs to be run during
# loading time of the shared lib that has it linked into, which is how the
# code gets called by the OS. This static lib might need to be linked as a
# whole archive dep for multiple shared libs, otherwise this static lib will
# be dropped by the linker since there are no incoming symbol references.
NO_EXPORTING = "NO_EXPORTING"

CcSharedLibraryPermissionsInfo = provider(
"Permissions for a cc shared library.",
fields = {
Expand All @@ -45,6 +54,7 @@ GraphNodeInfo = provider(
"children": "Other GraphNodeInfo from dependencies of this target",
"label": "Label of the target visited",
"linkable_more_than_once": "Linkable into more than a single cc_shared_library",
"no_exporting": "The static lib doesn't export any symbols so don't export it",
},
)
CcSharedLibraryInfo = provider(
Expand Down Expand Up @@ -532,7 +542,12 @@ def _cc_shared_library_impl(ctx):
runfiles = runfiles.merge(ctx.runfiles(files = precompiled_only_dynamic_libraries_runfiles))

for export in ctx.attr.roots:
exports[str(export.label)] = True
export_label = str(export.label)
if GraphNodeInfo in export and export[GraphNodeInfo].no_exporting:
if export_label in link_once_static_libs:
link_once_static_libs.remove(export_label)
continue
exports[export_label] = True

debug_files = []
exports_debug_file = ctx.actions.declare_file(ctx.label.name + "_exports.txt")
Expand Down Expand Up @@ -595,15 +610,19 @@ def _graph_structure_aspect_impl(target, ctx):
# TODO(bazel-team): Add flag to Bazel that can toggle the initialization of
# linkable_more_than_once.
linkable_more_than_once = False
no_exporting = False
if hasattr(ctx.rule.attr, "tags"):
for tag in ctx.rule.attr.tags:
if tag == LINKABLE_MORE_THAN_ONCE:
linkable_more_than_once = True
elif tag == NO_EXPORTING:
no_exporting = True

return [GraphNodeInfo(
label = ctx.label,
children = children,
linkable_more_than_once = linkable_more_than_once,
no_exporting = no_exporting,
)]

def _cc_shared_library_permissions_impl(ctx):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
load(":starlark_tests.bzl", "additional_inputs_test", "build_failure_test", "debug_files_test", "interface_library_output_group_test", "linking_suffix_test", "paths_test", "runfiles_test")
load(
":starlark_tests.bzl",
"additional_inputs_test",
"build_failure_test",
"debug_files_test",
"interface_library_output_group_test",
"linking_suffix_test",
"paths_test",
"runfiles_test",
"no_exporting_static_lib_test",
)

LINKABLE_MORE_THAN_ONCE = "LINKABLE_MORE_THAN_ONCE"

Expand Down Expand Up @@ -391,6 +401,51 @@ cc_shared_library_permissions(
],
)

cc_library(
name = "static_lib_no_exporting",
srcs = [
"bar.cc",
"bar.h",
],
tags = ["NO_EXPORTING"],
)

cc_library(
name = "static_lib_exporting",
srcs = [
"bar2.cc",
"bar2.h",
],
)

cc_shared_library(
name = "lib_with_no_exporting_roots_1",
roots = [":static_lib_no_exporting"],
)

cc_shared_library(
name = "lib_with_no_exporting_roots_2",
roots = [":static_lib_no_exporting"],
dynamic_deps = [":lib_with_no_exporting_roots_3"],
)

cc_shared_library(
name = "lib_with_no_exporting_roots_3",
roots = [":static_lib_no_exporting"],
)

cc_shared_library(
name = "lib_with_no_exporting_roots",
roots = [
":static_lib_no_exporting",
":static_lib_exporting",
],
dynamic_deps = [
":lib_with_no_exporting_roots_1",
":lib_with_no_exporting_roots_2",
],
)

build_failure_test(
name = "two_dynamic_deps_same_export_in_so_test",
message = "Two shared libraries in dependencies export the same symbols",
Expand Down Expand Up @@ -439,3 +494,8 @@ build_failure_test(
}),
target_under_test = "//src/main/starlark/tests/builtins_bzl/cc/cc_shared_library/test_cc_shared_library/failing_targets:unaccounted_for_libs_so",
)

no_exporting_static_lib_test(
name = "no_exporting_static_lib_test",
target_under_test = ":lib_with_no_exporting_roots",
)
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,21 @@ interface_library_output_group_test = analysistest.make(
"is_windows": attr.bool(),
},
)

def _no_exporting_static_lib_test_impl(ctx):
env = analysistest.begin(ctx)

target_under_test = analysistest.target_under_test(env)

# There should be only one exported file
actual_file = target_under_test[CcSharedLibraryInfo].exports[0]

# Sometimes "@" is prefixed in some test environments
expected = "//src/main/starlark/tests/builtins_bzl/cc/cc_shared_library/test_cc_shared_library:static_lib_exporting"
asserts.true(env, actual_file.endswith(expected))

return analysistest.end(env)

no_exporting_static_lib_test = analysistest.make(
_no_exporting_static_lib_test_impl,
)

0 comments on commit 5ba2d25

Please sign in to comment.