1# Copyright (C) 2021 The Android Open Source Project
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
15load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
16load("//build/bazel/rules:metadata.bzl", "MetadataFileInfo")
17load("//build/bazel/rules/cc:cc_library_common.bzl", "parse_apex_sdk_version")
18load("//build/bazel/rules/cc:cc_library_shared.bzl", "CcSharedLibraryOutputInfo", "CcStubLibrariesInfo")
19load("//build/bazel/rules/cc:cc_stub_library.bzl", "CcStubLibrarySharedInfo")
20load("//build/bazel/rules/cc:stripped_cc_common.bzl", "CcUnstrippedInfo")
21load("//build/bazel/rules/license:license_aspect.bzl", "license_aspect")
22
23ApexCcInfo = provider(
24    "Info needed to use CC targets in APEXes",
25    fields = {
26        "provides_native_libs": "Labels of native shared libs that this apex provides.",
27        "requires_native_libs": "Labels of native shared libs that this apex requires.",
28        "transitive_shared_libs": "File references to transitive .so libs produced by the CC targets and should be included in the APEX.",
29    },
30)
31
32ApexCcMkInfo = provider(
33    "AndroidMk data about CC targets in APEXes",
34    fields = {
35        "make_modules_to_install": "List of module names that should be installed into the system, along with this APEX",
36    },
37)
38
39# Special libraries that are installed to the bootstrap subdirectory. Bionic
40# libraries are assumed to be provided by the system, and installed automatically
41# as a symlink to the runtime APEX.
42#
43# This list is from https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/cc.go;l=1439-1452;drc=9c667416ded33b93a44c5f1894ea23cae6699a17
44#
45# NOTE: Keep this list in sync with the Soong list.
46#
47# See cc/binary.go#install for more information.
48def _installed_to_bootstrap(label):
49    label = str(label)
50
51    # hwasan
52    if label == "@//prebuilts/clang/host/linux-x86:libclang_rt.hwasan":
53        return True
54
55    # bionic libs
56    if label in [
57        "@//bionic/libc:libc",
58        "@//bionic/libc:libc_hwasan",  # For completeness, but no one should be depending on this.
59        "@//bionic/libm:libm",
60        "@//bionic/libdl:libdl",
61        "@//bionic/libdl_android:libdl_android",
62        "@//bionic/linker:linker",
63    ]:
64        return True
65
66    return False
67
68def has_cc_stubs(target):
69    """
70    Return True if this target provides stubs.
71
72    There is no need to check versions of stubs any more, see aosp/1609533.
73
74    These stable ABI libraries are intentionally omitted from APEXes as they are
75    provided from another APEX or the platform.  By omitting them from APEXes, we
76    ensure that there are no multiple copies of such libraries on a device.
77
78    Args:
79      target: The target to check for stubs on.
80    Returns:
81      If the target has cc stubs
82    """
83    if CcStubLibrarySharedInfo in target:
84        # This is a stub lib (direct or transitive).
85        return True
86
87    if CcStubLibrariesInfo in target and target[CcStubLibrariesInfo].has_stubs:
88        # Direct deps of the apex. The apex would depend on the source lib, not stub lib,
89        # so check for CcStubLibrariesInfo.has_stubs.
90        return True
91
92    return False
93
94# Check if this target is specified as a direct dependency of the APEX,
95# as opposed to a transitive dependency, as the transitivity impacts
96# the files that go into an APEX.
97def is_apex_direct_dep(label, ctx):
98    apex_direct_deps = ctx.attr._apex_direct_deps[BuildSettingInfo].value
99    return str(label) in apex_direct_deps
100
101MinSdkVersionInfo = provider(
102    "MinSdkVersionInfo provides metadata about the min_sdk_version attribute of a target",
103    fields = {
104        "apex_inherit": "true if min_sdk_version: \"apex_inherit\" is present on the module",
105        "min_sdk_version": "value of min_sdk_version",
106    },
107)
108
109def get_min_sdk_version(ctx):
110    """get_min_sdk_version returns the min_sdk_version for the existing target
111
112    Args:
113        ctx (rule context): a rule context
114    Returns:
115        MinSdkVersionInfo
116    """
117    min_sdk_version = None
118    apex_inherit = False
119    if hasattr(ctx.rule.attr, "min_sdk_version"):
120        if ctx.rule.attr.min_sdk_version == "apex_inherit":
121            apex_inherit = True
122        elif ctx.rule.attr.min_sdk_version:
123            min_sdk_version = parse_apex_sdk_version(ctx.rule.attr.min_sdk_version)
124    else:
125        # min_sdk_version in cc targets are represented as features
126        for f in ctx.rule.attr.features:
127            if f.startswith("sdk_version_"):
128                # e.g. sdk_version_29 or sdk_version_10000 or sdk_version_apex_inherit
129                sdk_version = f.removeprefix("sdk_version_")
130                if sdk_version == "apex_inherit":
131                    apex_inherit = True
132                elif min_sdk_version == None:
133                    min_sdk_version = int(sdk_version)
134                else:
135                    fail(
136                        "found more than one sdk_version feature on {target}; features = {features}",
137                        target = ctx.label,
138                        features = ctx.rule.attr.features,
139                    )
140    return MinSdkVersionInfo(
141        min_sdk_version = min_sdk_version,
142        apex_inherit = apex_inherit,
143    )
144
145def _validate_min_sdk_version(ctx):
146    dep_min_version = get_min_sdk_version(ctx).min_sdk_version
147    apex_min_version = parse_apex_sdk_version(ctx.attr._min_sdk_version[BuildSettingInfo].value)
148    if dep_min_version and apex_min_version < dep_min_version:
149        fail("The apex %s's min_sdk_version %s cannot be lower than the dep's min_sdk_version %s" %
150             (ctx.attr._apex_name[BuildSettingInfo].value, apex_min_version, dep_min_version))
151
152def _apex_cc_aspect_impl(target, ctx):
153    # Ensure that dependencies are compatible with this apex's min_sdk_level
154    if not ctx.attr.testonly:
155        _validate_min_sdk_version(ctx)
156
157    # Whether this dep is a direct dep of an APEX or makes a difference in dependency
158    # traversal, and aggregation of libs that are required from the platform/other APEXes,
159    # and libs that this APEX will provide to others.
160    is_direct_dep = is_apex_direct_dep(target.label, ctx)
161
162    provides = []
163    requires = []
164    make_modules_to_install = []
165
166    # The APEX manifest records the stub-providing libs (ABI-stable) in its
167    # direct and transitive deps.
168    #
169    # If a stub-providing lib is in the direct deps of an apex, then the apex
170    # provides the symbols.
171    #
172    # If a stub-providing lib is in the transitive deps of an apex, then the
173    # apex requires the symbols from the platform or other apexes.
174    if has_cc_stubs(target):
175        if is_direct_dep:
176            # Mark this target as "stub-providing" exports of this APEX,
177            # which the system and other APEXes can depend on, and propagate
178            # this list.
179            provides.append(target.label)
180        else:
181            # If this is not a direct dep and the build is in not unbundled mode,
182            # and stubs are available, don't propagate the libraries.
183
184            # Mark this target as required from the system either via
185            # the system partition, or another APEX, and propagate this list.
186            if CcStubLibrarySharedInfo not in target:
187                fail("Analysis of target: %s in apex: %s failed. This target does not provide CcStubLibrarySharedInfo. \
188This apex should likely use stubs of the target instead." % (target, ctx.attr._apex_name[BuildSettingInfo].value))
189            source_library_label = target[CcStubLibrarySharedInfo].source_library_label
190
191            # If a stub library is in the "provides" of the apex, it doesn't need to be in the "requires"
192            if not is_apex_direct_dep(source_library_label, ctx):
193                requires.append(source_library_label)
194                if not ctx.attr._unbundled_build[BuildSettingInfo].value and not _installed_to_bootstrap(source_library_label):
195                    # It's sufficient to pass the make module name, not the fully qualified bazel label.
196                    make_modules_to_install.append(source_library_label.name)
197
198            return [
199                ApexCcInfo(
200                    transitive_shared_libs = depset(),
201                    requires_native_libs = depset(direct = requires),
202                    provides_native_libs = depset(direct = provides),
203                ),
204                ApexCcMkInfo(
205                    make_modules_to_install = depset(direct = make_modules_to_install),
206                ),
207            ]
208
209    shared_object_files = []
210
211    # Transitive deps containing shared libraries to be propagated the apex.
212    transitive_deps = []
213    rules_propagate_src = [
214        "_bssl_hash_injection",
215        "stripped_shared_library",
216        "versioned_shared_library",
217        "stripped_binary",
218        "versioned_binary",
219    ]
220
221    # Exclude the stripped and unstripped so files
222    if ctx.rule.kind == "_cc_library_shared_proxy":
223        shared_object_files.append(struct(
224            stripped = target[CcSharedLibraryOutputInfo].output_file,
225            unstripped = target[CcUnstrippedInfo].unstripped,
226            metadata_file = target[MetadataFileInfo].metadata_file,
227            generating_rule_owner = target[CcSharedLibraryOutputInfo].output_file.owner,
228        ))
229        if hasattr(ctx.rule.attr, "shared"):
230            transitive_deps.append(ctx.rule.attr.shared[0])
231    elif ctx.rule.kind == "cc_prebuilt_library_shared":
232        files = target[DefaultInfo].files.to_list()
233        if len(files) != 1:
234            fail("expected only 1 file in %s[DefaultInfo].files, but got %d" % (target.label, len(files)))
235        shared_object_files.append(struct(
236            # TODO: This file needs to actually be stripped.
237            stripped = files[0],
238            unstripped = files[0],
239            metadata_file = None,
240            # Normally the generating_rule_owner is the owner of the stripped
241            # output file, but the owner of files[0] has slashes in its name,
242            # and the APEX's make_module_name must not contain a slash.
243            generating_rule_owner = target.label,
244        ))
245    elif ctx.rule.kind in ["cc_shared_library", "cc_binary"]:
246        # Propagate along the dynamic_deps edges for binaries and shared libs
247        if hasattr(ctx.rule.attr, "dynamic_deps"):
248            for dep in ctx.rule.attr.dynamic_deps:
249                transitive_deps.append(dep)
250    elif ctx.rule.kind in rules_propagate_src and hasattr(ctx.rule.attr, "src"):
251        # Propagate along the src edge
252        if ctx.rule.kind == "stripped_binary":
253            transitive_deps.append(ctx.rule.attr.src[0])
254        else:
255            transitive_deps.append(ctx.rule.attr.src)
256
257    # We only collect runtime dependencies from binaries and shared libraries,
258    # we _explicitly_ omit static libraries (kind = _cc_library_combiner)
259    if ctx.rule.kind in ["stripped_binary", "_cc_library_shared_proxy"] and hasattr(ctx.rule.attr, "runtime_deps"):
260        for dep in ctx.rule.attr.runtime_deps:
261            unstripped = None
262            if CcUnstrippedInfo in dep:
263                unstripped = dep[CcUnstrippedInfo].unstripped
264            for output_file in dep[DefaultInfo].files.to_list():
265                if output_file.extension == "so":
266                    shared_object_files.append(struct(
267                        stripped = output_file,
268                        unstripped = unstripped,
269                        metadata_file = dep[MetadataFileInfo].metadata_file,
270                        generating_rule_owner = output_file.owner,
271                    ))
272            transitive_deps.append(dep)
273
274    return [
275        ApexCcInfo(
276            transitive_shared_libs = depset(
277                shared_object_files,
278                transitive = [info[ApexCcInfo].transitive_shared_libs for info in transitive_deps],
279            ),
280            requires_native_libs = depset(
281                [],
282                transitive = [info[ApexCcInfo].requires_native_libs for info in transitive_deps],
283            ),
284            provides_native_libs = depset(
285                provides,
286                transitive = [info[ApexCcInfo].provides_native_libs for info in transitive_deps],
287            ),
288        ),
289        ApexCcMkInfo(
290            make_modules_to_install = depset(
291                [],
292                transitive = [info[ApexCcMkInfo].make_modules_to_install for info in transitive_deps],
293            ),
294        ),
295    ]
296
297# The list of attributes in a cc dep graph where this aspect will traverse on.
298CC_ATTR_ASPECTS = [
299    "dynamic_deps",
300    "deps",
301    "shared",
302    "src",
303    "runtime_deps",
304    "static_deps",
305    "whole_archive_deps",
306]
307
308# This aspect is intended to be applied on a apex.native_shared_libs attribute
309apex_cc_aspect = aspect(
310    implementation = _apex_cc_aspect_impl,
311    provides = [ApexCcInfo, ApexCcMkInfo],
312    attrs = {
313        # This is propagated from the apex
314        "testonly": attr.bool(default = False),
315        "_apex_direct_deps": attr.label(default = "//build/bazel/rules/apex:apex_direct_deps"),
316        "_apex_name": attr.label(default = "//build/bazel/rules/apex:apex_name"),
317        "_min_sdk_version": attr.label(default = "//build/bazel/rules/apex:min_sdk_version"),
318        "_unbundled_build": attr.label(default = "//build/bazel/product_config:unbundled_build"),
319    },
320    attr_aspects = CC_ATTR_ASPECTS,
321    requires = [license_aspect],
322    # TODO: Have this aspect also propagate along attributes of native_shared_libs?
323)
324