xref: /aosp_15_r20/external/bazelbuild-rules_license/rules/gather_licenses_info.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/private:gathering_providers.bzl",
24*f578df4fSJingwen Chen    "TransitiveLicensesInfo",
25*f578df4fSJingwen Chen)
26*f578df4fSJingwen Chen
27*f578df4fSJingwen Chen# Definition for compliance namespace, used for filtering licenses
28*f578df4fSJingwen Chen# based on the namespace to which they belong.
29*f578df4fSJingwen ChenNAMESPACES = ["compliance"]
30*f578df4fSJingwen Chen
31*f578df4fSJingwen Chendef _strip_null_repo(label):
32*f578df4fSJingwen Chen    """Removes the null repo name (e.g. @//) from a string.
33*f578df4fSJingwen Chen
34*f578df4fSJingwen Chen    The is to make str(label) compatible between bazel 5.x and 6.x
35*f578df4fSJingwen Chen    """
36*f578df4fSJingwen Chen    s = str(label)
37*f578df4fSJingwen Chen    if s.startswith('@//'):
38*f578df4fSJingwen Chen        return s[1:]
39*f578df4fSJingwen Chen    elif s.startswith('@@//'):
40*f578df4fSJingwen Chen        return s[2:]
41*f578df4fSJingwen Chen    return s
42*f578df4fSJingwen Chen
43*f578df4fSJingwen Chendef _gather_licenses_info_impl(target, ctx):
44*f578df4fSJingwen Chen    return gather_metadata_info_common(target, ctx, TransitiveLicensesInfo, NAMESPACES, [], should_traverse)
45*f578df4fSJingwen Chen
46*f578df4fSJingwen Chengather_licenses_info = aspect(
47*f578df4fSJingwen Chen    doc = """Collects LicenseInfo providers into a single TransitiveLicensesInfo provider.""",
48*f578df4fSJingwen Chen    implementation = _gather_licenses_info_impl,
49*f578df4fSJingwen Chen    attr_aspects = ["*"],
50*f578df4fSJingwen Chen    attrs = {
51*f578df4fSJingwen Chen        "_trace": attr.label(default = "@rules_license//rules:trace_target"),
52*f578df4fSJingwen Chen    },
53*f578df4fSJingwen Chen    provides = [TransitiveLicensesInfo],
54*f578df4fSJingwen Chen    apply_to_generating_rules = True,
55*f578df4fSJingwen Chen)
56*f578df4fSJingwen Chen
57*f578df4fSJingwen Chendef _write_licenses_info_impl(target, ctx):
58*f578df4fSJingwen Chen    """Write transitive license info into a JSON file
59*f578df4fSJingwen Chen
60*f578df4fSJingwen Chen    Args:
61*f578df4fSJingwen Chen      target: The target of the aspect.
62*f578df4fSJingwen Chen      ctx: The aspect evaluation context.
63*f578df4fSJingwen Chen
64*f578df4fSJingwen Chen    Returns:
65*f578df4fSJingwen Chen      OutputGroupInfo
66*f578df4fSJingwen Chen    """
67*f578df4fSJingwen Chen
68*f578df4fSJingwen Chen    if not TransitiveLicensesInfo in target:
69*f578df4fSJingwen Chen        return [OutputGroupInfo(licenses = depset())]
70*f578df4fSJingwen Chen    info = target[TransitiveLicensesInfo]
71*f578df4fSJingwen Chen    outs = []
72*f578df4fSJingwen Chen
73*f578df4fSJingwen Chen    # If the result doesn't contain licenses, we simply return the provider
74*f578df4fSJingwen Chen    if not hasattr(info, "target_under_license"):
75*f578df4fSJingwen Chen        return [OutputGroupInfo(licenses = depset())]
76*f578df4fSJingwen Chen
77*f578df4fSJingwen Chen    # Write the output file for the target
78*f578df4fSJingwen Chen    name = "%s_licenses_info.json" % ctx.label.name
79*f578df4fSJingwen Chen    content = "[\n%s\n]\n" % ",\n".join(licenses_info_to_json(info))
80*f578df4fSJingwen Chen    out = ctx.actions.declare_file(name)
81*f578df4fSJingwen Chen    ctx.actions.write(
82*f578df4fSJingwen Chen        output = out,
83*f578df4fSJingwen Chen        content = content,
84*f578df4fSJingwen Chen    )
85*f578df4fSJingwen Chen    outs.append(out)
86*f578df4fSJingwen Chen
87*f578df4fSJingwen Chen    if ctx.attr._trace[TraceInfo].trace:
88*f578df4fSJingwen Chen        trace = ctx.actions.declare_file("%s_trace_info.json" % ctx.label.name)
89*f578df4fSJingwen Chen        ctx.actions.write(output = trace, content = "\n".join(info.traces))
90*f578df4fSJingwen Chen        outs.append(trace)
91*f578df4fSJingwen Chen
92*f578df4fSJingwen Chen    return [OutputGroupInfo(licenses = depset(outs))]
93*f578df4fSJingwen Chen
94*f578df4fSJingwen Chengather_licenses_info_and_write = aspect(
95*f578df4fSJingwen Chen    doc = """Collects TransitiveLicensesInfo providers and writes JSON representation to a file.
96*f578df4fSJingwen Chen
97*f578df4fSJingwen Chen    Usage:
98*f578df4fSJingwen Chen      blaze build //some:target \
99*f578df4fSJingwen Chen          --aspects=@rules_license//rules:gather_licenses_info.bzl%gather_licenses_info_and_write
100*f578df4fSJingwen Chen          --output_groups=licenses
101*f578df4fSJingwen Chen    """,
102*f578df4fSJingwen Chen    implementation = _write_licenses_info_impl,
103*f578df4fSJingwen Chen    attr_aspects = ["*"],
104*f578df4fSJingwen Chen    attrs = {
105*f578df4fSJingwen Chen        "_trace": attr.label(default = "@rules_license//rules:trace_target"),
106*f578df4fSJingwen Chen    },
107*f578df4fSJingwen Chen    provides = [OutputGroupInfo],
108*f578df4fSJingwen Chen    requires = [gather_licenses_info],
109*f578df4fSJingwen Chen    apply_to_generating_rules = True,
110*f578df4fSJingwen Chen)
111*f578df4fSJingwen Chen
112*f578df4fSJingwen Chendef write_licenses_info(ctx, deps, json_out):
113*f578df4fSJingwen Chen    """Writes TransitiveLicensesInfo providers for a set of targets as JSON.
114*f578df4fSJingwen Chen
115*f578df4fSJingwen Chen    TODO(aiuto): Document JSON schema. But it is under development, so the current
116*f578df4fSJingwen Chen    best place to look is at tests/hello_licenses.golden.
117*f578df4fSJingwen Chen
118*f578df4fSJingwen Chen    Usage:
119*f578df4fSJingwen Chen      write_licenses_info must be called from a rule implementation, where the
120*f578df4fSJingwen Chen      rule has run the gather_licenses_info aspect on its deps to
121*f578df4fSJingwen Chen      collect the transitive closure of LicenseInfo providers into a
122*f578df4fSJingwen Chen      LicenseInfo provider.
123*f578df4fSJingwen Chen
124*f578df4fSJingwen Chen      foo = rule(
125*f578df4fSJingwen Chen        implementation = _foo_impl,
126*f578df4fSJingwen Chen        attrs = {
127*f578df4fSJingwen Chen           "deps": attr.label_list(aspects = [gather_licenses_info])
128*f578df4fSJingwen Chen        }
129*f578df4fSJingwen Chen      )
130*f578df4fSJingwen Chen
131*f578df4fSJingwen Chen      def _foo_impl(ctx):
132*f578df4fSJingwen Chen        ...
133*f578df4fSJingwen Chen        out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name)
134*f578df4fSJingwen Chen        write_licenses_info(ctx, ctx.attr.deps, licenses_file)
135*f578df4fSJingwen Chen
136*f578df4fSJingwen Chen    Args:
137*f578df4fSJingwen Chen      ctx: context of the caller
138*f578df4fSJingwen Chen      deps: a list of deps which should have TransitiveLicensesInfo providers.
139*f578df4fSJingwen Chen            This requires that you have run the gather_licenses_info
140*f578df4fSJingwen Chen            aspect over them
141*f578df4fSJingwen Chen      json_out: output handle to write the JSON info
142*f578df4fSJingwen Chen    """
143*f578df4fSJingwen Chen    licenses = []
144*f578df4fSJingwen Chen    for dep in deps:
145*f578df4fSJingwen Chen        if TransitiveLicensesInfo in dep:
146*f578df4fSJingwen Chen            licenses.extend(licenses_info_to_json(dep[TransitiveLicensesInfo]))
147*f578df4fSJingwen Chen    ctx.actions.write(
148*f578df4fSJingwen Chen        output = json_out,
149*f578df4fSJingwen Chen        content = "[\n%s\n]\n" % ",\n".join(licenses),
150*f578df4fSJingwen Chen    )
151*f578df4fSJingwen Chen
152*f578df4fSJingwen Chendef licenses_info_to_json(licenses_info):
153*f578df4fSJingwen Chen    """Render a single LicenseInfo provider to JSON
154*f578df4fSJingwen Chen
155*f578df4fSJingwen Chen    Args:
156*f578df4fSJingwen Chen      licenses_info: A LicenseInfo.
157*f578df4fSJingwen Chen
158*f578df4fSJingwen Chen    Returns:
159*f578df4fSJingwen Chen      [(str)] list of LicenseInfo values rendered as JSON.
160*f578df4fSJingwen Chen    """
161*f578df4fSJingwen Chen
162*f578df4fSJingwen Chen    main_template = """  {{
163*f578df4fSJingwen Chen    "top_level_target": "{top_level_target}",
164*f578df4fSJingwen Chen    "dependencies": [{dependencies}
165*f578df4fSJingwen Chen    ],
166*f578df4fSJingwen Chen    "licenses": [{licenses}
167*f578df4fSJingwen Chen    ]\n  }}"""
168*f578df4fSJingwen Chen
169*f578df4fSJingwen Chen    dep_template = """
170*f578df4fSJingwen Chen      {{
171*f578df4fSJingwen Chen        "target_under_license": "{target_under_license}",
172*f578df4fSJingwen Chen        "licenses": [
173*f578df4fSJingwen Chen          {licenses}
174*f578df4fSJingwen Chen        ]
175*f578df4fSJingwen Chen      }}"""
176*f578df4fSJingwen Chen
177*f578df4fSJingwen Chen    # TODO(aiuto): 'rule' is a duplicate of 'label' until old users are transitioned
178*f578df4fSJingwen Chen    license_template = """
179*f578df4fSJingwen Chen      {{
180*f578df4fSJingwen Chen        "label": "{label}",
181*f578df4fSJingwen Chen        "rule": "{label}",
182*f578df4fSJingwen Chen        "license_kinds": [{kinds}
183*f578df4fSJingwen Chen        ],
184*f578df4fSJingwen Chen        "copyright_notice": "{copyright_notice}",
185*f578df4fSJingwen Chen        "package_name": "{package_name}",
186*f578df4fSJingwen Chen        "package_url": "{package_url}",
187*f578df4fSJingwen Chen        "package_version": "{package_version}",
188*f578df4fSJingwen Chen        "license_text": "{license_text}",
189*f578df4fSJingwen Chen        "used_by": [
190*f578df4fSJingwen Chen          {used_by}
191*f578df4fSJingwen Chen        ]
192*f578df4fSJingwen Chen      }}"""
193*f578df4fSJingwen Chen
194*f578df4fSJingwen Chen    kind_template = """
195*f578df4fSJingwen Chen          {{
196*f578df4fSJingwen Chen            "target": "{kind_path}",
197*f578df4fSJingwen Chen            "name": "{kind_name}",
198*f578df4fSJingwen Chen            "conditions": {kind_conditions}
199*f578df4fSJingwen Chen          }}"""
200*f578df4fSJingwen Chen
201*f578df4fSJingwen Chen    # Build reverse map of license to user
202*f578df4fSJingwen Chen    used_by = {}
203*f578df4fSJingwen Chen    for dep in licenses_info.deps.to_list():
204*f578df4fSJingwen Chen        # Undo the concatenation applied when stored in the provider.
205*f578df4fSJingwen Chen        dep_licenses = dep.licenses.split(",")
206*f578df4fSJingwen Chen        for license in dep_licenses:
207*f578df4fSJingwen Chen            if license not in used_by:
208*f578df4fSJingwen Chen                used_by[license] = []
209*f578df4fSJingwen Chen            used_by[license].append(_strip_null_repo(dep.target_under_license))
210*f578df4fSJingwen Chen
211*f578df4fSJingwen Chen    all_licenses = []
212*f578df4fSJingwen Chen    for license in sorted(licenses_info.licenses.to_list(), key = lambda x: x.label):
213*f578df4fSJingwen Chen        kinds = []
214*f578df4fSJingwen Chen        for kind in sorted(license.license_kinds, key = lambda x: x.name):
215*f578df4fSJingwen Chen            kinds.append(kind_template.format(
216*f578df4fSJingwen Chen                kind_name = kind.name,
217*f578df4fSJingwen Chen                kind_path = kind.label,
218*f578df4fSJingwen Chen                kind_conditions = kind.conditions,
219*f578df4fSJingwen Chen            ))
220*f578df4fSJingwen Chen
221*f578df4fSJingwen Chen        if license.license_text:
222*f578df4fSJingwen Chen            # Special handling for synthetic LicenseInfo
223*f578df4fSJingwen Chen            text_path = (license.license_text.package + "/" + license.license_text.name if type(license.license_text) == "Label" else license.license_text.path)
224*f578df4fSJingwen Chen            all_licenses.append(license_template.format(
225*f578df4fSJingwen Chen                copyright_notice = license.copyright_notice,
226*f578df4fSJingwen Chen                kinds = ",".join(kinds),
227*f578df4fSJingwen Chen                license_text = text_path,
228*f578df4fSJingwen Chen                package_name = license.package_name,
229*f578df4fSJingwen Chen                package_url = license.package_url,
230*f578df4fSJingwen Chen                package_version = license.package_version,
231*f578df4fSJingwen Chen                label = _strip_null_repo(license.label),
232*f578df4fSJingwen Chen                used_by = ",\n          ".join(sorted(['"%s"' % x for x in used_by[str(license.label)]])),
233*f578df4fSJingwen Chen            ))
234*f578df4fSJingwen Chen
235*f578df4fSJingwen Chen    all_deps = []
236*f578df4fSJingwen Chen    for dep in sorted(licenses_info.deps.to_list(), key = lambda x: x.target_under_license):
237*f578df4fSJingwen Chen        licenses_used = []
238*f578df4fSJingwen Chen
239*f578df4fSJingwen Chen        # Undo the concatenation applied when stored in the provider.
240*f578df4fSJingwen Chen        dep_licenses = dep.licenses.split(",")
241*f578df4fSJingwen Chen        all_deps.append(dep_template.format(
242*f578df4fSJingwen Chen            target_under_license = _strip_null_repo(dep.target_under_license),
243*f578df4fSJingwen Chen            licenses = ",\n          ".join(sorted(['"%s"' % _strip_null_repo(x) for x in dep_licenses])),
244*f578df4fSJingwen Chen        ))
245*f578df4fSJingwen Chen
246*f578df4fSJingwen Chen    return [main_template.format(
247*f578df4fSJingwen Chen        top_level_target = _strip_null_repo(licenses_info.target_under_license),
248*f578df4fSJingwen Chen        dependencies = ",".join(all_deps),
249*f578df4fSJingwen Chen        licenses = ",".join(all_licenses),
250*f578df4fSJingwen Chen    )]
251