xref: /aosp_15_r20/tools/asuite/atest/bazel/resources/rules/soong_prebuilt.bzl (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
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
15"""Rule used to import artifacts prebuilt by Soong into the Bazel workspace.
16
17The rule returns a DefaultInfo provider with all artifacts and runtime dependencies,
18and a SoongPrebuiltInfo provider with the original Soong module name, artifacts,
19runtime dependencies and data dependencies.
20"""
21
22load("//bazel/rules:platform_transitions.bzl", "device_transition")
23load("//bazel/rules:common_settings.bzl", "BuildSettingInfo")
24
25SoongPrebuiltInfo = provider(
26    doc = "Info about a prebuilt Soong build module",
27    fields = {
28        "module_name": "Name of the original Soong build module",
29        # This field contains this target's outputs and all runtime dependency
30        # outputs.
31        "transitive_runtime_outputs": "Files required in the runtime environment",
32        "transitive_test_files": "Files of test modules",
33        "platform_flavor": "The platform flavor that this target will be built on",
34    },
35)
36
37def _soong_prebuilt_impl(ctx):
38    files = ctx.files.files
39
40    # Ensure that soong_prebuilt targets always have at least one file to avoid
41    # evaluation errors when running Bazel cquery on a clean tree to find
42    # dependencies.
43    #
44    # This happens because soong_prebuilt dependency target globs don't match
45    # any files when the workspace symlinks are broken and point to build
46    # artifacts that still don't exist. This in turn causes errors in rules
47    # that reference these targets via attributes with allow_single_file=True
48    # and which expect a file to be present.
49    #
50    # Note that the below action is never really executed during cquery
51    # evaluation but fails when run as part of a test execution to signal that
52    # prebuilts were not correctly imported.
53    if not files:
54        placeholder_file = ctx.actions.declare_file(ctx.label.name + ".missing")
55
56        progress_message = (
57            "Attempting to import missing artifacts for Soong module '%s'; " +
58            "please make sure that the module is built with Soong before " +
59            "running Bazel"
60        ) % ctx.attr.module_name
61
62        # Note that we don't write the file for the action to always be
63        # executed and display the warning message.
64        ctx.actions.run_shell(
65            outputs = [placeholder_file],
66            command = "/bin/false",
67            progress_message = progress_message,
68        )
69        files = [placeholder_file]
70
71    runfiles = ctx.runfiles(files = files).merge_all([
72        dep[DefaultInfo].default_runfiles
73        for dep in ctx.attr.runtime_deps + ctx.attr.data + ctx.attr.device_data
74    ])
75
76    # We exclude the outputs of static dependencies from the runfiles since
77    # they're already embedded in this target's output. Note that this is done
78    # recursively such that only transitive runtime dependency outputs are
79    # included. For example, in a chain A -> B -> C -> D where B and C are
80    # statically linked, only A's and D's outputs would remain in the runfiles.
81    runfiles = runfiles.merge_all([
82        ctx.runfiles(
83            files = _exclude_files(
84                dep[DefaultInfo].default_runfiles.files,
85                dep[DefaultInfo].files,
86            ).to_list(),
87        )
88        for dep in ctx.attr.static_deps
89    ])
90
91    return [
92        _make_soong_prebuilt_info(
93            ctx.attr.module_name,
94            ctx.attr._platform_flavor[BuildSettingInfo].value,
95            files = files,
96            runtime_deps = ctx.attr.runtime_deps,
97            static_deps = ctx.attr.static_deps,
98            data = ctx.attr.data,
99            device_data = ctx.attr.device_data,
100            suites = ctx.attr.suites,
101        ),
102        DefaultInfo(
103            files = depset(files),
104            runfiles = runfiles,
105        ),
106    ]
107
108soong_prebuilt = rule(
109    attrs = {
110        "module_name": attr.string(),
111        # Artifacts prebuilt by Soong.
112        "files": attr.label_list(allow_files = True),
113        # Targets that are needed by this target during runtime.
114        "runtime_deps": attr.label_list(),
115        # Note that while the outputs of static deps are not required for test
116        # execution we include them since they have their own runtime
117        # dependencies.
118        "static_deps": attr.label_list(),
119        "data": attr.label_list(),
120        "device_data": attr.label_list(
121            cfg = device_transition,
122        ),
123        "suites": attr.string_list(),
124        "_platform_flavor": attr.label(default = "//bazel/rules:platform_flavor"),
125        # This attribute is required to use Starlark transitions. It allows
126        # allowlisting usage of this rule. For more information, see
127        # https://docs.bazel.build/versions/master/skylark/config.html#user-defined-transitions
128        "_allowlist_function_transition": attr.label(
129            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
130        ),
131    },
132    implementation = _soong_prebuilt_impl,
133    doc = "A rule that imports artifacts prebuilt by Soong into the Bazel workspace",
134)
135
136def _soong_uninstalled_prebuilt_impl(ctx):
137    runfiles = ctx.runfiles().merge_all([
138        dep[DefaultInfo].default_runfiles
139        for dep in ctx.attr.runtime_deps
140    ])
141
142    return [
143        _make_soong_prebuilt_info(
144            ctx.attr.module_name,
145            ctx.attr._platform_flavor[BuildSettingInfo].value,
146            runtime_deps = ctx.attr.runtime_deps,
147        ),
148        DefaultInfo(
149            runfiles = runfiles,
150        ),
151    ]
152
153soong_uninstalled_prebuilt = rule(
154    attrs = {
155        "module_name": attr.string(),
156        "runtime_deps": attr.label_list(),
157        "_platform_flavor": attr.label(default = "//bazel/rules:platform_flavor"),
158    },
159    implementation = _soong_uninstalled_prebuilt_impl,
160    doc = "A rule for targets with no runtime outputs",
161)
162
163def _make_soong_prebuilt_info(
164        module_name,
165        platform_flavor,
166        files = [],
167        runtime_deps = [],
168        static_deps = [],
169        data = [],
170        device_data = [],
171        suites = []):
172    """Build a SoongPrebuiltInfo based on the given information.
173
174    Args:
175        runtime_deps: List of runtime dependencies required by this target.
176        static_deps: List of static dependencies required by this target.
177        data: List of data required by this target.
178        device_data: List of data on device variant required by this target.
179        suites: List of test suites this target belongs to.
180
181    Returns:
182        An instance of SoongPrebuiltInfo.
183    """
184    transitive_runtime_outputs = [
185        dep[SoongPrebuiltInfo].transitive_runtime_outputs
186        for dep in runtime_deps
187    ]
188
189    # We exclude the outputs of static dependencies and data dependencies from
190    # the transitive runtime outputs since static dependencies are already
191    # embedded in this target's output and the data dependencies shouldn't be
192    # present in the runtime paths. Note that this is done recursively such that
193    # only transitive runtime dependency outputs are included. For example, in a
194    # chain A -> B -> C -> D where B and C are statically linked or data
195    # dependencies, only A's and D's outputs would remain in the transitive
196    # runtime outputs.
197    transitive_runtime_outputs.extend([
198        _exclude_files(
199            dep[SoongPrebuiltInfo].transitive_runtime_outputs,
200            dep[DefaultInfo].files,
201        )
202        for dep in static_deps + data
203    ])
204    return SoongPrebuiltInfo(
205        module_name = module_name,
206        platform_flavor = platform_flavor,
207        transitive_runtime_outputs = depset(files, transitive = transitive_runtime_outputs),
208        transitive_test_files = depset(
209            # Note that `suites` is never empty for test files. This because
210            # test build modules that do not explicitly specify a `test_suites`
211            # Soong attribute belong to `null-suite`.
212            files if suites else [],
213            transitive = [
214                dep[SoongPrebuiltInfo].transitive_test_files
215                for dep in data + device_data + runtime_deps
216            ],
217        ),
218    )
219
220def _exclude_files(all_files, files_to_exclude):
221    files = []
222    files_to_exclude = {f: None for f in files_to_exclude.to_list()}
223    for f in all_files.to_list():
224        if f not in files_to_exclude:
225            files.append(f)
226    return depset(files)
227