unplugged-system/tools/asuite/atest/bazel/resources/rules/soong_prebuilt.bzl

227 lines
8.7 KiB
Python
Raw Normal View History

# Copyright (C) 2021 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Rule used to import artifacts prebuilt by Soong into the Bazel workspace.
The rule returns a DefaultInfo provider with all artifacts and runtime dependencies,
and a SoongPrebuiltInfo provider with the original Soong module name, artifacts,
runtime dependencies and data dependencies.
"""
load("//bazel/rules:platform_transitions.bzl", "device_transition")
load("//bazel/rules:common_settings.bzl", "BuildSettingInfo")
SoongPrebuiltInfo = provider(
doc = "Info about a prebuilt Soong build module",
fields = {
"module_name": "Name of the original Soong build module",
# This field contains this target's outputs and all runtime dependency
# outputs.
"transitive_runtime_outputs": "Files required in the runtime environment",
"transitive_test_files": "Files of test modules",
"platform_flavor": "The platform flavor that this target will be built on",
},
)
def _soong_prebuilt_impl(ctx):
files = ctx.files.files
# Ensure that soong_prebuilt targets always have at least one file to avoid
# evaluation errors when running Bazel cquery on a clean tree to find
# dependencies.
#
# This happens because soong_prebuilt dependency target globs don't match
# any files when the workspace symlinks are broken and point to build
# artifacts that still don't exist. This in turn causes errors in rules
# that reference these targets via attributes with allow_single_file=True
# and which expect a file to be present.
#
# Note that the below action is never really executed during cquery
# evaluation but fails when run as part of a test execution to signal that
# prebuilts were not correctly imported.
if not files:
placeholder_file = ctx.actions.declare_file(ctx.label.name + ".missing")
progress_message = (
"Attempting to import missing artifacts for Soong module '%s'; " +
"please make sure that the module is built with Soong before " +
"running Bazel"
) % ctx.attr.module_name
# Note that we don't write the file for the action to always be
# executed and display the warning message.
ctx.actions.run_shell(
outputs = [placeholder_file],
command = "/bin/false",
progress_message = progress_message,
)
files = [placeholder_file]
runfiles = ctx.runfiles(files = files).merge_all([
dep[DefaultInfo].default_runfiles
for dep in ctx.attr.runtime_deps + ctx.attr.data + ctx.attr.device_data
])
# We exclude the outputs of static dependencies from the runfiles since
# they're already embedded in this target's output. Note that this is done
# recursively such that only transitive runtime dependency outputs are
# included. For example, in a chain A -> B -> C -> D where B and C are
# statically linked, only A's and D's outputs would remain in the runfiles.
runfiles = runfiles.merge_all([
ctx.runfiles(
files = _exclude_files(
dep[DefaultInfo].default_runfiles.files,
dep[DefaultInfo].files,
).to_list(),
)
for dep in ctx.attr.static_deps
])
return [
_make_soong_prebuilt_info(
ctx.attr.module_name,
ctx.attr._platform_flavor[BuildSettingInfo].value,
files = files,
runtime_deps = ctx.attr.runtime_deps,
static_deps = ctx.attr.static_deps,
data = ctx.attr.data,
device_data = ctx.attr.device_data,
suites = ctx.attr.suites,
),
DefaultInfo(
files = depset(files),
runfiles = runfiles,
),
]
soong_prebuilt = rule(
attrs = {
"module_name": attr.string(),
# Artifacts prebuilt by Soong.
"files": attr.label_list(allow_files = True),
# Targets that are needed by this target during runtime.
"runtime_deps": attr.label_list(),
# Note that while the outputs of static deps are not required for test
# execution we include them since they have their own runtime
# dependencies.
"static_deps": attr.label_list(),
"data": attr.label_list(),
"device_data": attr.label_list(
cfg = device_transition,
),
"suites": attr.string_list(),
"_platform_flavor": attr.label(default = "//bazel/rules:platform_flavor"),
# This attribute is required to use Starlark transitions. It allows
# allowlisting usage of this rule. For more information, see
# https://docs.bazel.build/versions/master/skylark/config.html#user-defined-transitions
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
implementation = _soong_prebuilt_impl,
doc = "A rule that imports artifacts prebuilt by Soong into the Bazel workspace",
)
def _soong_uninstalled_prebuilt_impl(ctx):
runfiles = ctx.runfiles().merge_all([
dep[DefaultInfo].default_runfiles
for dep in ctx.attr.runtime_deps
])
return [
_make_soong_prebuilt_info(
ctx.attr.module_name,
ctx.attr._platform_flavor[BuildSettingInfo].value,
runtime_deps = ctx.attr.runtime_deps,
),
DefaultInfo(
runfiles = runfiles,
),
]
soong_uninstalled_prebuilt = rule(
attrs = {
"module_name": attr.string(),
"runtime_deps": attr.label_list(),
"_platform_flavor": attr.label(default = "//bazel/rules:platform_flavor"),
},
implementation = _soong_uninstalled_prebuilt_impl,
doc = "A rule for targets with no runtime outputs",
)
def _make_soong_prebuilt_info(
module_name,
platform_flavor,
files = [],
runtime_deps = [],
static_deps = [],
data = [],
device_data = [],
suites = []):
"""Build a SoongPrebuiltInfo based on the given information.
Args:
runtime_deps: List of runtime dependencies required by this target.
static_deps: List of static dependencies required by this target.
data: List of data required by this target.
device_data: List of data on device variant required by this target.
suites: List of test suites this target belongs to.
Returns:
An instance of SoongPrebuiltInfo.
"""
transitive_runtime_outputs = [
dep[SoongPrebuiltInfo].transitive_runtime_outputs
for dep in runtime_deps
]
# We exclude the outputs of static dependencies and data dependencies from
# the transitive runtime outputs since static dependencies are already
# embedded in this target's output and the data dependencies shouldn't be
# present in the runtime paths. Note that this is done recursively such that
# only transitive runtime dependency outputs are included. For example, in a
# chain A -> B -> C -> D where B and C are statically linked or data
# dependencies, only A's and D's outputs would remain in the transitive
# runtime outputs.
transitive_runtime_outputs.extend([
_exclude_files(
dep[SoongPrebuiltInfo].transitive_runtime_outputs,
dep[DefaultInfo].files,
)
for dep in static_deps + data
])
return SoongPrebuiltInfo(
module_name = module_name,
platform_flavor = platform_flavor,
transitive_runtime_outputs = depset(files, transitive = transitive_runtime_outputs),
transitive_test_files = depset(
# Note that `suites` is never empty for test files. This because
# test build modules that do not explicitly specify a `test_suites`
# Soong attribute belong to `null-suite`.
files if suites else [],
transitive = [
dep[SoongPrebuiltInfo].transitive_test_files
for dep in data + device_data + runtime_deps
],
),
)
def _exclude_files(all_files, files_to_exclude):
files = []
files_to_exclude = {f: None for f in files_to_exclude.to_list()}
for f in all_files.to_list():
if f not in files_to_exclude:
files.append(f)
return depset(files)