blob: ef2519f73ccb6dbbfdedeec0e4d98669fd26bbc3 [file]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Aspect and rules to collect FIDL JSON data."""
load("//build/bazel/rules/fidl:providers.bzl", "FidlLibraryInfo")
load("//build/bazel/rules/idk:providers.bzl", "FuchsiaIdkAtomInfo", "FuchsiaIdkMoleculeInfo")
visibility(["//build/fidl/..."])
FidlMetadataAspectInfo = provider(
doc = "Collects struct metadata for FIDL libraries.",
fields = {
# LINT.IfChange(idk_fidl_json_data_fields)
"metadata": """A depset of structs containing IDK FIDL JSON data collected.
Each struct has the following fields:
- name: Name of the FIDL library (string).
- ir: Path to the FIDL IR JSON file relative to the source root (string).
- category: The FIDL library's category if any (string).
- sdk_area: The FIDL library's API area (string).
""",
# LINT.ThenChange(:sdk_fidl_json_data_contents, :sdk_fidl_json_data_json)
},
)
def _fidl_metadata_aspect_impl(target, ctx):
metadata = []
if FuchsiaIdkAtomInfo in target:
atom_info = target[FuchsiaIdkAtomInfo]
if atom_info.type == "fidl_library":
if not (hasattr(ctx.rule.attr, "atom_build_deps") and len(ctx.rule.attr.atom_build_deps) > 1):
fail("FIDL library atom must have more than one atom_build_dep.")
ir_file = None
for dep in ctx.rule.attr.atom_build_deps:
if FidlLibraryInfo in dep:
if len(dep[DefaultInfo].files.to_list()) != 1:
fail("`FidlLibraryInfo` target `%s` must have exactly one file." % dep.label)
ir_file = dep[DefaultInfo].files.to_list()[0]
if not ir_file.basename.endswith(".fidl.json"):
fail("Unexpected file `%s` found in `FidlLibraryInfo` target `%s.`" % (
ir_file.path,
dep.label,
))
break
if not ir_file:
fail("Could not find IR JSON file in FIDL library atom.")
# LINT.IfChange(idk_fidl_json_data_contents)
metadata.append(struct(
# The IDK name is the library name.
name = atom_info.idk_name,
ir = ir_file.path,
category = atom_info.category,
sdk_area = getattr(atom_info, "api_area", "Unknown"),
))
# LINT.ThenChange(:sdk_fidl_json_data_fields, :sdk_fidl_json_data_json, //build/fidl/fidl_library.gni:sdk_fidl_json_data_contents)
transitive = []
for dep in ctx.rule.attr.deps:
if FidlMetadataAspectInfo in dep:
transitive.append(dep[FidlMetadataAspectInfo].metadata)
return [FidlMetadataAspectInfo(
metadata = depset(direct = metadata, transitive = transitive),
)]
# Collects metadata for FIDL library IDK atoms.
# This aspect only traverses IDK molecules and IDK atoms. Both reference
# other such targets via `deps`.
fidl_metadata_aspect = aspect(
implementation = _fidl_metadata_aspect_impl,
attr_aspects = ["deps"],
)
def _collect_fidl_metadata_impl(ctx):
metadata_dict = {}
for dep in ctx.attr.deps:
if FidlMetadataAspectInfo in dep:
for data in dep[FidlMetadataAspectInfo].metadata.to_list():
metadata_dict[data.name] = data
json_data = [
# LINT.IfChange(idk_fidl_json_data_json)
{
"name": entry.name,
"ir": entry.ir,
"category": entry.category,
"sdk_area": entry.sdk_area,
}
# LINT.ThenChange(:sdk_fidl_json_data_fields, :sdk_fidl_json_data_contents, //build/fidl/fidl_library.gni:sdk_fidl_json_data_contents)
for entry in metadata_dict.values()
if entry.category in ctx.attr.categories
]
json_data = sorted(json_data, key = lambda x: x["name"])
out_file = ctx.actions.declare_file(ctx.label.name + ".json")
ctx.actions.write(out_file, json.encode_indent(json_data, indent = " "))
return [DefaultInfo(files = depset([out_file]))]
collect_fidl_metadata = rule(
doc = "Collects metadata for FIDL library IDK atoms in a dependency graph into `<name>.json`. " +
"Only follows public `deps`. Thus, it does not collect privately " +
"used FIDL libraries, such as those used in the implementation of " +
"a prebuilt library IDK atom.",
implementation = _collect_fidl_metadata_impl,
attrs = {
"deps": attr.label_list(
doc = "List of dependency graph root IDK atoms from which to collect metadata.",
providers = [[FuchsiaIdkAtomInfo], [FuchsiaIdkMoleculeInfo]],
aspects = [fidl_metadata_aspect],
mandatory = True,
),
"categories": attr.string_list(
doc = "Categories for which to collect metadata. Libraries in other categories will be ignored.",
mandatory = True,
allow_empty = False,
),
},
)
# To locate information about a FIDL library in `FidlLibraryInfo`, we have to
# find the FIDL IR JSON target. Thus, we must ensure that the attribute names
# for all possible paths to that target have the aspect applied and are checked
# in the aspect.
# TODO(https://fxbug.dev/496603528): Find a more robust way to ensure that
# `FidlLibraryInfo` is found. Otherwise, this is fragile and must cover all
# bindings types.
_dependency_attrs_to_check_for_fidl_library_info = [
# Normal dependency attributes.
"deps",
"implementation_deps",
"data",
# The FIDL IR target is added to the FIDL atom's `atom_build_deps`.
"atom_build_deps",
# Attributes used in the generation of [hl]cpp bindings.
"fidl_ir_json",
"generated_fidl_cc_bindings",
"hdrs",
"srcs",
]
FidlIrJsonAspectInfo = provider(
doc = "Collects the paths of IR JSON files.",
fields = {
"ir_json_files": """A depset of string paths for IR files collected.
Paths are relative to the source root.
""",
},
)
def _fidl_ir_json_aspect_impl(target, ctx):
ir_json_files = []
if FidlLibraryInfo in target:
for f in target[DefaultInfo].files.to_list():
if f.basename.endswith(".fidl.json"):
ir_json_files.append(f)
break
transitive = []
for attr_name in _dependency_attrs_to_check_for_fidl_library_info:
if hasattr(ctx.rule.attr, attr_name):
attr_value = getattr(ctx.rule.attr, attr_name)
dep_targets = attr_value if type(attr_value) == "list" else [attr_value]
for dep in dep_targets:
if FidlIrJsonAspectInfo in dep:
transitive.append(dep[FidlIrJsonAspectInfo].ir_json_files)
return [FidlIrJsonAspectInfo(
ir_json_files = depset(direct = ir_json_files, transitive = transitive),
)]
# Collects the paths of IR JSON files.
fidl_ir_json_aspect = aspect(
implementation = _fidl_ir_json_aspect_impl,
attr_aspects = _dependency_attrs_to_check_for_fidl_library_info,
)
def _collect_fidl_ir_json_files_impl(ctx):
ir_json_files_set = set()
for dep in ctx.attr.deps:
if FidlIrJsonAspectInfo in dep:
ir_json_files_set.update([
file.path
for file in dep[FidlIrJsonAspectInfo].ir_json_files.to_list()
])
paths = sorted(ir_json_files_set)
out_file = ctx.actions.declare_file(ctx.label.name + ".txt")
ctx.actions.write(out_file, "\n".join(paths) + "\n")
return [DefaultInfo(files = depset([out_file]))]
collect_fidl_ir_json_files = rule(
doc = """Collects the paths of all FIDL IR JSON files in a dependency graph into `<name>.txt`.
The files are listed unstructured, one per line. Paths are relative to the source root.
""",
implementation = _collect_fidl_ir_json_files_impl,
attrs = {
"deps": attr.label_list(
doc = "List of dependency graph root targets from which to collect IR JSON files.",
aspects = [fidl_ir_json_aspect],
mandatory = True,
allow_empty = False,
),
},
)