1load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cpp_toolchain")
2
3def cc_library_static(
4        name,
5        srcs = [],
6        deps = [],
7        hdrs = [],
8        copts = [],
9        includes = [],
10        native_bridge_supported = False,  # TODO: not supported yet.
11        whole_archive_deps = [],
12        **kwargs):
13    "Bazel macro to correspond with the cc_library_static Soong module."
14    mainlib_name = "%s_mainlib" % name
15
16    # Silently drop these attributes for now:
17    # - native_bridge_supported
18    native.cc_library(
19        name = mainlib_name,
20        srcs = srcs,
21        hdrs = hdrs,
22        # TODO(b/187533117): Handle whole_archive_deps differently from regular static deps.
23        deps = deps + whole_archive_deps,
24        copts = copts,
25        includes = includes,
26        **kwargs
27    )
28
29    # Safeguard target to handle the no-srcs no-deps case.
30    # With no-srcs no-deps, this returns a stub. Otherwise, it's a passthrough no-op.
31    _empty_library_safeguard(
32        name = name,
33        deps = [mainlib_name],
34    )
35
36# Returns a cloned copy of the given CcInfo object, except that all linker inputs
37# with owner `old_owner_label` are recreated and owned by the current target.
38#
39# This is useful in the "macro with proxy rule" pattern, as some rules upstream
40# may expect they are depending directly on a target which generates linker inputs,
41# as opposed to a proxy target which is a level of indirection to such a target.
42def _claim_ownership(ctx, old_owner_label, ccinfo):
43    linker_inputs = []
44    # This is not ideal, as it flattens a depset.
45    for old_linker_input in ccinfo.linking_context.linker_inputs.to_list():
46        if old_linker_input.owner == old_owner_label:
47            new_linker_input = cc_common.create_linker_input(
48                owner = ctx.label,
49                libraries = depset(direct = old_linker_input.libraries))
50            linker_inputs.append(new_linker_input)
51        else:
52            linker_inputs.append(old_linker_input)
53
54    linking_context = cc_common.create_linking_context(linker_inputs = depset(direct = linker_inputs))
55    return CcInfo(compilation_context = ccinfo.compilation_context, linking_context = linking_context)
56
57def _empty_library_safeguard_impl(ctx):
58    if len(ctx.attr.deps) != 1:
59        fail("the deps attribute should always contain exactly one label")
60
61    main_target = ctx.attr.deps[0]
62    if len(ctx.files.deps) > 0:
63        # This safeguard is a no-op, as a library was generated by the main target.
64        new_cc_info = _claim_ownership(ctx, main_target.label, main_target[CcInfo])
65        return [new_cc_info, main_target[DefaultInfo]]
66
67    # The main library is empty; link a stub and propagate it to match Soong behavior.
68    cc_toolchain = find_cpp_toolchain(ctx)
69    CPP_LINK_STATIC_LIBRARY_ACTION_NAME = "c++-link-static-library"
70    feature_configuration = cc_common.configure_features(
71        ctx = ctx,
72        cc_toolchain = cc_toolchain,
73        requested_features = ctx.features,
74        unsupported_features = ctx.disabled_features + ["linker_flags"],
75    )
76
77    output_file = ctx.actions.declare_file(ctx.label.name + ".a")
78    linker_input = cc_common.create_linker_input(
79        owner = ctx.label,
80        libraries = depset(direct = [
81            cc_common.create_library_to_link(
82                actions = ctx.actions,
83                feature_configuration = feature_configuration,
84                cc_toolchain = cc_toolchain,
85                static_library = output_file,
86            ),
87        ]),
88    )
89    compilation_context = cc_common.create_compilation_context()
90    linking_context = cc_common.create_linking_context(linker_inputs = depset(direct = [linker_input]))
91
92    archiver_path = cc_common.get_tool_for_action(
93        feature_configuration = feature_configuration,
94        action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME,
95    )
96    archiver_variables = cc_common.create_link_variables(
97        feature_configuration = feature_configuration,
98        cc_toolchain = cc_toolchain,
99        output_file = output_file.path,
100        is_using_linker = False,
101    )
102    command_line = cc_common.get_memory_inefficient_command_line(
103        feature_configuration = feature_configuration,
104        action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME,
105        variables = archiver_variables,
106    )
107    args = ctx.actions.args()
108    args.add_all(command_line)
109
110    ctx.actions.run(
111        executable = archiver_path,
112        arguments = [args],
113        inputs = depset(
114            transitive = [
115                cc_toolchain.all_files,
116            ],
117        ),
118        outputs = [output_file],
119    )
120
121    cc_info = cc_common.merge_cc_infos(cc_infos = [
122        main_target[CcInfo],
123        CcInfo(compilation_context = compilation_context, linking_context = linking_context),
124    ])
125    return [
126        DefaultInfo(files = depset([output_file])),
127        cc_info,
128    ]
129
130# A rule which depends on a single cc_library target. If the cc_library target
131# has no outputs (indicating that it has no srcs or deps), then this safeguard
132# rule creates a single stub .a file using llvm-ar. This mimics Soong's behavior
133# in this regard. Otherwise, this safeguard is a simple passthrough for the providers
134# of the cc_library.
135_empty_library_safeguard = rule(
136    implementation = _empty_library_safeguard_impl,
137    attrs = {
138        # This should really be a label attribute since it always contains a
139        # single dependency, but cc_shared_library requires that C++ rules
140        # depend on each other through the "deps" attribute.
141        "deps": attr.label_list(providers = [CcInfo]),
142        "_cc_toolchain": attr.label(
143            default = Label("@local_config_cc//:toolchain"),
144            providers = [cc_common.CcToolchainInfo],
145        ),
146    },
147    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
148    fragments = ["cpp"],
149)
150