xref: /aosp_15_r20/external/bazelbuild-rules_license/rules/gather_metadata.bzl (revision f578df4fd057ffe2023728444759535685631548)
1*f578df4fSJingwen Chen# Copyright 2022 Google LLC
2*f578df4fSJingwen Chen#
3*f578df4fSJingwen Chen# Licensed under the Apache License, Version 2.0 (the "License");
4*f578df4fSJingwen Chen# you may not use this file except in compliance with the License.
5*f578df4fSJingwen Chen# You may obtain a copy of the License at
6*f578df4fSJingwen Chen#
7*f578df4fSJingwen Chen# https://www.apache.org/licenses/LICENSE-2.0
8*f578df4fSJingwen Chen#
9*f578df4fSJingwen Chen# Unless required by applicable law or agreed to in writing, software
10*f578df4fSJingwen Chen# distributed under the License is distributed on an "AS IS" BASIS,
11*f578df4fSJingwen Chen# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*f578df4fSJingwen Chen# See the License for the specific language governing permissions and
13*f578df4fSJingwen Chen# limitations under the License.
14*f578df4fSJingwen Chen"""Rules and macros for collecting LicenseInfo providers."""
15*f578df4fSJingwen Chen
16*f578df4fSJingwen Chenload(
17*f578df4fSJingwen Chen    "@rules_license//rules:licenses_core.bzl",
18*f578df4fSJingwen Chen    "TraceInfo",
19*f578df4fSJingwen Chen    "gather_metadata_info_common",
20*f578df4fSJingwen Chen    "should_traverse",
21*f578df4fSJingwen Chen)
22*f578df4fSJingwen Chenload(
23*f578df4fSJingwen Chen    "@rules_license//rules:providers.bzl",
24*f578df4fSJingwen Chen    "ExperimentalMetadataInfo",
25*f578df4fSJingwen Chen    "PackageInfo",
26*f578df4fSJingwen Chen)
27*f578df4fSJingwen Chenload(
28*f578df4fSJingwen Chen    "@rules_license//rules/private:gathering_providers.bzl",
29*f578df4fSJingwen Chen    "TransitiveMetadataInfo",
30*f578df4fSJingwen Chen)
31*f578df4fSJingwen Chen
32*f578df4fSJingwen Chen# Definition for compliance namespace, used for filtering licenses
33*f578df4fSJingwen Chen# based on the namespace to which they belong.
34*f578df4fSJingwen ChenNAMESPACES = ["compliance"]
35*f578df4fSJingwen Chen
36*f578df4fSJingwen Chendef _strip_null_repo(label):
37*f578df4fSJingwen Chen    """Removes the null repo name (e.g. @//) from a string.
38*f578df4fSJingwen Chen
39*f578df4fSJingwen Chen    The is to make str(label) compatible between bazel 5.x and 6.x
40*f578df4fSJingwen Chen    """
41*f578df4fSJingwen Chen    s = str(label)
42*f578df4fSJingwen Chen    if s.startswith('@//'):
43*f578df4fSJingwen Chen        return s[1:]
44*f578df4fSJingwen Chen    elif s.startswith('@@//'):
45*f578df4fSJingwen Chen        return s[2:]
46*f578df4fSJingwen Chen    return s
47*f578df4fSJingwen Chen
48*f578df4fSJingwen Chendef _bazel_package(label):
49*f578df4fSJingwen Chen    clean_label = _strip_null_repo(label)
50*f578df4fSJingwen Chen    return clean_label[0:-(len(label.name) + 1)]
51*f578df4fSJingwen Chen
52*f578df4fSJingwen Chendef _gather_metadata_info_impl(target, ctx):
53*f578df4fSJingwen Chen    return gather_metadata_info_common(
54*f578df4fSJingwen Chen        target,
55*f578df4fSJingwen Chen        ctx,
56*f578df4fSJingwen Chen        TransitiveMetadataInfo,
57*f578df4fSJingwen Chen        NAMESPACES,
58*f578df4fSJingwen Chen        [ExperimentalMetadataInfo, PackageInfo],
59*f578df4fSJingwen Chen        should_traverse)
60*f578df4fSJingwen Chen
61*f578df4fSJingwen Chengather_metadata_info = aspect(
62*f578df4fSJingwen Chen    doc = """Collects LicenseInfo providers into a single TransitiveMetadataInfo provider.""",
63*f578df4fSJingwen Chen    implementation = _gather_metadata_info_impl,
64*f578df4fSJingwen Chen    attr_aspects = ["*"],
65*f578df4fSJingwen Chen    attrs = {
66*f578df4fSJingwen Chen        "_trace": attr.label(default = "@rules_license//rules:trace_target"),
67*f578df4fSJingwen Chen    },
68*f578df4fSJingwen Chen    provides = [TransitiveMetadataInfo],
69*f578df4fSJingwen Chen    apply_to_generating_rules = True,
70*f578df4fSJingwen Chen)
71*f578df4fSJingwen Chen
72*f578df4fSJingwen Chendef _write_metadata_info_impl(target, ctx):
73*f578df4fSJingwen Chen    """Write transitive license info into a JSON file
74*f578df4fSJingwen Chen
75*f578df4fSJingwen Chen    Args:
76*f578df4fSJingwen Chen      target: The target of the aspect.
77*f578df4fSJingwen Chen      ctx: The aspect evaluation context.
78*f578df4fSJingwen Chen
79*f578df4fSJingwen Chen    Returns:
80*f578df4fSJingwen Chen      OutputGroupInfo
81*f578df4fSJingwen Chen    """
82*f578df4fSJingwen Chen
83*f578df4fSJingwen Chen    if not TransitiveMetadataInfo in target:
84*f578df4fSJingwen Chen        return [OutputGroupInfo(licenses = depset())]
85*f578df4fSJingwen Chen    info = target[TransitiveMetadataInfo]
86*f578df4fSJingwen Chen    outs = []
87*f578df4fSJingwen Chen
88*f578df4fSJingwen Chen    # If the result doesn't contain licenses, we simply return the provider
89*f578df4fSJingwen Chen    if not hasattr(info, "target_under_license"):
90*f578df4fSJingwen Chen        return [OutputGroupInfo(licenses = depset())]
91*f578df4fSJingwen Chen
92*f578df4fSJingwen Chen    # Write the output file for the target
93*f578df4fSJingwen Chen    name = "%s_metadata_info.json" % ctx.label.name
94*f578df4fSJingwen Chen    content = "[\n%s\n]\n" % ",\n".join(metadata_info_to_json(info))
95*f578df4fSJingwen Chen    out = ctx.actions.declare_file(name)
96*f578df4fSJingwen Chen    ctx.actions.write(
97*f578df4fSJingwen Chen        output = out,
98*f578df4fSJingwen Chen        content = content,
99*f578df4fSJingwen Chen    )
100*f578df4fSJingwen Chen    outs.append(out)
101*f578df4fSJingwen Chen
102*f578df4fSJingwen Chen    if ctx.attr._trace[TraceInfo].trace:
103*f578df4fSJingwen Chen        trace = ctx.actions.declare_file("%s_trace_info.json" % ctx.label.name)
104*f578df4fSJingwen Chen        ctx.actions.write(output = trace, content = "\n".join(info.traces))
105*f578df4fSJingwen Chen        outs.append(trace)
106*f578df4fSJingwen Chen
107*f578df4fSJingwen Chen    return [OutputGroupInfo(licenses = depset(outs))]
108*f578df4fSJingwen Chen
109*f578df4fSJingwen Chengather_metadata_info_and_write = aspect(
110*f578df4fSJingwen Chen    doc = """Collects TransitiveMetadataInfo providers and writes JSON representation to a file.
111*f578df4fSJingwen Chen
112*f578df4fSJingwen Chen    Usage:
113*f578df4fSJingwen Chen      bazel build //some:target \
114*f578df4fSJingwen Chen          --aspects=@rules_license//rules:gather_metadata_info.bzl%gather_metadata_info_and_write
115*f578df4fSJingwen Chen          --output_groups=licenses
116*f578df4fSJingwen Chen    """,
117*f578df4fSJingwen Chen    implementation = _write_metadata_info_impl,
118*f578df4fSJingwen Chen    attr_aspects = ["*"],
119*f578df4fSJingwen Chen    attrs = {
120*f578df4fSJingwen Chen        "_trace": attr.label(default = "@rules_license//rules:trace_target"),
121*f578df4fSJingwen Chen    },
122*f578df4fSJingwen Chen    provides = [OutputGroupInfo],
123*f578df4fSJingwen Chen    requires = [gather_metadata_info],
124*f578df4fSJingwen Chen    apply_to_generating_rules = True,
125*f578df4fSJingwen Chen)
126*f578df4fSJingwen Chen
127*f578df4fSJingwen Chendef write_metadata_info(ctx, deps, json_out):
128*f578df4fSJingwen Chen    """Writes TransitiveMetadataInfo providers for a set of targets as JSON.
129*f578df4fSJingwen Chen
130*f578df4fSJingwen Chen    TODO(aiuto): Document JSON schema. But it is under development, so the current
131*f578df4fSJingwen Chen    best place to look is at tests/hello_licenses.golden.
132*f578df4fSJingwen Chen
133*f578df4fSJingwen Chen    Usage:
134*f578df4fSJingwen Chen      write_metadata_info must be called from a rule implementation, where the
135*f578df4fSJingwen Chen      rule has run the gather_metadata_info aspect on its deps to
136*f578df4fSJingwen Chen      collect the transitive closure of LicenseInfo providers into a
137*f578df4fSJingwen Chen      LicenseInfo provider.
138*f578df4fSJingwen Chen
139*f578df4fSJingwen Chen      foo = rule(
140*f578df4fSJingwen Chen        implementation = _foo_impl,
141*f578df4fSJingwen Chen        attrs = {
142*f578df4fSJingwen Chen           "deps": attr.label_list(aspects = [gather_metadata_info])
143*f578df4fSJingwen Chen        }
144*f578df4fSJingwen Chen      )
145*f578df4fSJingwen Chen
146*f578df4fSJingwen Chen      def _foo_impl(ctx):
147*f578df4fSJingwen Chen        ...
148*f578df4fSJingwen Chen        out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name)
149*f578df4fSJingwen Chen        write_metadata_info(ctx, ctx.attr.deps, metadata_file)
150*f578df4fSJingwen Chen
151*f578df4fSJingwen Chen    Args:
152*f578df4fSJingwen Chen      ctx: context of the caller
153*f578df4fSJingwen Chen      deps: a list of deps which should have TransitiveMetadataInfo providers.
154*f578df4fSJingwen Chen            This requires that you have run the gather_metadata_info
155*f578df4fSJingwen Chen            aspect over them
156*f578df4fSJingwen Chen      json_out: output handle to write the JSON info
157*f578df4fSJingwen Chen    """
158*f578df4fSJingwen Chen    licenses = []
159*f578df4fSJingwen Chen    for dep in deps:
160*f578df4fSJingwen Chen        if TransitiveMetadataInfo in dep:
161*f578df4fSJingwen Chen            licenses.extend(metadata_info_to_json(dep[TransitiveMetadataInfo]))
162*f578df4fSJingwen Chen    ctx.actions.write(
163*f578df4fSJingwen Chen        output = json_out,
164*f578df4fSJingwen Chen        content = "[\n%s\n]\n" % ",\n".join(licenses),
165*f578df4fSJingwen Chen    )
166*f578df4fSJingwen Chen
167*f578df4fSJingwen Chendef metadata_info_to_json(metadata_info):
168*f578df4fSJingwen Chen    """Render a single LicenseInfo provider to JSON
169*f578df4fSJingwen Chen
170*f578df4fSJingwen Chen    Args:
171*f578df4fSJingwen Chen      metadata_info: A LicenseInfo.
172*f578df4fSJingwen Chen
173*f578df4fSJingwen Chen    Returns:
174*f578df4fSJingwen Chen      [(str)] list of LicenseInfo values rendered as JSON.
175*f578df4fSJingwen Chen    """
176*f578df4fSJingwen Chen
177*f578df4fSJingwen Chen    main_template = """  {{
178*f578df4fSJingwen Chen    "top_level_target": "{top_level_target}",
179*f578df4fSJingwen Chen    "dependencies": [{dependencies}
180*f578df4fSJingwen Chen    ],
181*f578df4fSJingwen Chen    "licenses": [{licenses}
182*f578df4fSJingwen Chen    ],
183*f578df4fSJingwen Chen    "packages": [{packages}
184*f578df4fSJingwen Chen    ]\n  }}"""
185*f578df4fSJingwen Chen
186*f578df4fSJingwen Chen    dep_template = """
187*f578df4fSJingwen Chen      {{
188*f578df4fSJingwen Chen        "target_under_license": "{target_under_license}",
189*f578df4fSJingwen Chen        "licenses": [
190*f578df4fSJingwen Chen          {licenses}
191*f578df4fSJingwen Chen        ]
192*f578df4fSJingwen Chen      }}"""
193*f578df4fSJingwen Chen
194*f578df4fSJingwen Chen    license_template = """
195*f578df4fSJingwen Chen      {{
196*f578df4fSJingwen Chen        "label": "{label}",
197*f578df4fSJingwen Chen        "bazel_package": "{bazel_package}",
198*f578df4fSJingwen Chen        "license_kinds": [{kinds}
199*f578df4fSJingwen Chen        ],
200*f578df4fSJingwen Chen        "copyright_notice": "{copyright_notice}",
201*f578df4fSJingwen Chen        "package_name": "{package_name}",
202*f578df4fSJingwen Chen        "package_url": "{package_url}",
203*f578df4fSJingwen Chen        "package_version": "{package_version}",
204*f578df4fSJingwen Chen        "license_text": "{license_text}",
205*f578df4fSJingwen Chen        "used_by": [
206*f578df4fSJingwen Chen          {used_by}
207*f578df4fSJingwen Chen        ]
208*f578df4fSJingwen Chen      }}"""
209*f578df4fSJingwen Chen
210*f578df4fSJingwen Chen    kind_template = """
211*f578df4fSJingwen Chen          {{
212*f578df4fSJingwen Chen            "target": "{kind_path}",
213*f578df4fSJingwen Chen            "name": "{kind_name}",
214*f578df4fSJingwen Chen            "conditions": {kind_conditions}
215*f578df4fSJingwen Chen          }}"""
216*f578df4fSJingwen Chen
217*f578df4fSJingwen Chen    package_info_template = """
218*f578df4fSJingwen Chen          {{
219*f578df4fSJingwen Chen            "target": "{label}",
220*f578df4fSJingwen Chen            "bazel_package": "{bazel_package}",
221*f578df4fSJingwen Chen            "package_name": "{package_name}",
222*f578df4fSJingwen Chen            "package_url": "{package_url}",
223*f578df4fSJingwen Chen            "package_version": "{package_version}"
224*f578df4fSJingwen Chen          }}"""
225*f578df4fSJingwen Chen
226*f578df4fSJingwen Chen    # Build reverse map of license to user
227*f578df4fSJingwen Chen    used_by = {}
228*f578df4fSJingwen Chen    for dep in metadata_info.deps.to_list():
229*f578df4fSJingwen Chen        # Undo the concatenation applied when stored in the provider.
230*f578df4fSJingwen Chen        dep_licenses = dep.licenses.split(",")
231*f578df4fSJingwen Chen        for license in dep_licenses:
232*f578df4fSJingwen Chen            if license not in used_by:
233*f578df4fSJingwen Chen                used_by[license] = []
234*f578df4fSJingwen Chen            used_by[license].append(_strip_null_repo(dep.target_under_license))
235*f578df4fSJingwen Chen
236*f578df4fSJingwen Chen    all_licenses = []
237*f578df4fSJingwen Chen    for license in sorted(metadata_info.licenses.to_list(), key = lambda x: x.label):
238*f578df4fSJingwen Chen        kinds = []
239*f578df4fSJingwen Chen        for kind in sorted(license.license_kinds, key = lambda x: x.name):
240*f578df4fSJingwen Chen            kinds.append(kind_template.format(
241*f578df4fSJingwen Chen                kind_name = kind.name,
242*f578df4fSJingwen Chen                kind_path = kind.label,
243*f578df4fSJingwen Chen                kind_conditions = kind.conditions,
244*f578df4fSJingwen Chen            ))
245*f578df4fSJingwen Chen
246*f578df4fSJingwen Chen        if license.license_text:
247*f578df4fSJingwen Chen            # Special handling for synthetic LicenseInfo
248*f578df4fSJingwen Chen            text_path = (license.license_text.package + "/" + license.license_text.name if type(license.license_text) == "Label" else license.license_text.path)
249*f578df4fSJingwen Chen            all_licenses.append(license_template.format(
250*f578df4fSJingwen Chen                copyright_notice = license.copyright_notice,
251*f578df4fSJingwen Chen                kinds = ",".join(kinds),
252*f578df4fSJingwen Chen                license_text = text_path,
253*f578df4fSJingwen Chen                package_name = license.package_name,
254*f578df4fSJingwen Chen                package_url = license.package_url,
255*f578df4fSJingwen Chen                package_version = license.package_version,
256*f578df4fSJingwen Chen                label = _strip_null_repo(license.label),
257*f578df4fSJingwen Chen                bazel_package =  _bazel_package(license.label),
258*f578df4fSJingwen Chen                used_by = ",\n          ".join(sorted(['"%s"' % x for x in used_by[str(license.label)]])),
259*f578df4fSJingwen Chen            ))
260*f578df4fSJingwen Chen
261*f578df4fSJingwen Chen    all_deps = []
262*f578df4fSJingwen Chen    for dep in sorted(metadata_info.deps.to_list(), key = lambda x: x.target_under_license):
263*f578df4fSJingwen Chen        # Undo the concatenation applied when stored in the provider.
264*f578df4fSJingwen Chen        dep_licenses = dep.licenses.split(",")
265*f578df4fSJingwen Chen        all_deps.append(dep_template.format(
266*f578df4fSJingwen Chen            target_under_license = _strip_null_repo(dep.target_under_license),
267*f578df4fSJingwen Chen            licenses = ",\n          ".join(sorted(['"%s"' % _strip_null_repo(x) for x in dep_licenses])),
268*f578df4fSJingwen Chen        ))
269*f578df4fSJingwen Chen
270*f578df4fSJingwen Chen    all_packages = []
271*f578df4fSJingwen Chen    # We would use this if we had distinct depsets for every provider type.
272*f578df4fSJingwen Chen    #for package in sorted(metadata_info.package_info.to_list(), key = lambda x: x.label):
273*f578df4fSJingwen Chen    #    all_packages.append(package_info_template.format(
274*f578df4fSJingwen Chen    #        label = _strip_null_repo(package.label),
275*f578df4fSJingwen Chen    #        package_name = package.package_name,
276*f578df4fSJingwen Chen    #        package_url = package.package_url,
277*f578df4fSJingwen Chen    #        package_version = package.package_version,
278*f578df4fSJingwen Chen    #    ))
279*f578df4fSJingwen Chen
280*f578df4fSJingwen Chen    for mi in sorted(metadata_info.other_metadata.to_list(), key = lambda x: x.label):
281*f578df4fSJingwen Chen        # Maybe use a map of provider class to formatter.  A generic dict->json function
282*f578df4fSJingwen Chen        # in starlark would help
283*f578df4fSJingwen Chen
284*f578df4fSJingwen Chen        # This format is for using distinct providers.  I like the compile time safety.
285*f578df4fSJingwen Chen        if mi.type == "package_info":
286*f578df4fSJingwen Chen            all_packages.append(package_info_template.format(
287*f578df4fSJingwen Chen                label = _strip_null_repo(mi.label),
288*f578df4fSJingwen Chen                bazel_package =  _bazel_package(mi.label),
289*f578df4fSJingwen Chen                package_name = mi.package_name,
290*f578df4fSJingwen Chen                package_url = mi.package_url,
291*f578df4fSJingwen Chen                package_version = mi.package_version,
292*f578df4fSJingwen Chen            ))
293*f578df4fSJingwen Chen        # experimental: Support the ExperimentalMetadataInfo bag of data
294*f578df4fSJingwen Chen        if mi.type == "package_info_alt":
295*f578df4fSJingwen Chen            all_packages.append(package_info_template.format(
296*f578df4fSJingwen Chen                label = _strip_null_repo(mi.label),
297*f578df4fSJingwen Chen                bazel_package =  _bazel_package(mi.label),
298*f578df4fSJingwen Chen                # data is just a bag, so we need to use get() or ""
299*f578df4fSJingwen Chen                package_name = mi.data.get("package_name") or "",
300*f578df4fSJingwen Chen                package_url = mi.data.get("package_url") or "",
301*f578df4fSJingwen Chen                package_version = mi.data.get("package_version") or "",
302*f578df4fSJingwen Chen            ))
303*f578df4fSJingwen Chen
304*f578df4fSJingwen Chen    return [main_template.format(
305*f578df4fSJingwen Chen        top_level_target = _strip_null_repo(metadata_info.target_under_license),
306*f578df4fSJingwen Chen        dependencies = ",".join(all_deps),
307*f578df4fSJingwen Chen        licenses = ",".join(all_licenses),
308*f578df4fSJingwen Chen        packages = ",".join(all_packages),
309*f578df4fSJingwen Chen    )]
310