xref: /aosp_15_r20/build/bazel/rules/license/license_aspect.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Workerload("@rules_license//rules:providers.bzl", "LicenseInfo")
2*7594170eSAndroid Build Coastguard Workerload("//build/bazel/rules:metadata.bzl", "MetadataFileInfo")
3*7594170eSAndroid Build Coastguard Worker
4*7594170eSAndroid Build Coastguard WorkerRuleLicensedDependenciesInfo = provider(
5*7594170eSAndroid Build Coastguard Worker    doc = """Rule's licensed dependencies.""",
6*7594170eSAndroid Build Coastguard Worker    fields = dict(
7*7594170eSAndroid Build Coastguard Worker        license_closure = "depset(license) for the rule and its licensed dependencies",
8*7594170eSAndroid Build Coastguard Worker    ),
9*7594170eSAndroid Build Coastguard Worker)
10*7594170eSAndroid Build Coastguard Worker
11*7594170eSAndroid Build Coastguard Workerdef _maybe_expand(rule, transitive_licenses):
12*7594170eSAndroid Build Coastguard Worker    if not RuleLicensedDependenciesInfo in rule:
13*7594170eSAndroid Build Coastguard Worker        return
14*7594170eSAndroid Build Coastguard Worker    dep_info = rule[RuleLicensedDependenciesInfo]
15*7594170eSAndroid Build Coastguard Worker    if hasattr(dep_info, "license_closure"):
16*7594170eSAndroid Build Coastguard Worker        transitive_licenses.append(dep_info.license_closure)
17*7594170eSAndroid Build Coastguard Worker
18*7594170eSAndroid Build Coastguard Workerdef create_metadata_file_info(ctx):
19*7594170eSAndroid Build Coastguard Worker    if hasattr(ctx.rule.attr, "applicable_licenses"):
20*7594170eSAndroid Build Coastguard Worker        for lic in ctx.rule.attr.applicable_licenses:
21*7594170eSAndroid Build Coastguard Worker            files = lic.files.to_list()
22*7594170eSAndroid Build Coastguard Worker            if len(files) == 1 and files[0].basename == "METADATA":
23*7594170eSAndroid Build Coastguard Worker                return MetadataFileInfo(metadata_file = files[0])
24*7594170eSAndroid Build Coastguard Worker
25*7594170eSAndroid Build Coastguard Worker    return MetadataFileInfo(metadata_file = None)
26*7594170eSAndroid Build Coastguard Worker
27*7594170eSAndroid Build Coastguard Workerdef _rule_licenses_aspect_impl(_rule, ctx):
28*7594170eSAndroid Build Coastguard Worker    if ctx.rule.kind == "_license":
29*7594170eSAndroid Build Coastguard Worker        return [RuleLicensedDependenciesInfo(), MetadataFileInfo()]
30*7594170eSAndroid Build Coastguard Worker
31*7594170eSAndroid Build Coastguard Worker    licenses = []
32*7594170eSAndroid Build Coastguard Worker    transitive_licenses = []
33*7594170eSAndroid Build Coastguard Worker    if hasattr(ctx.rule.attr, "applicable_licenses"):
34*7594170eSAndroid Build Coastguard Worker        licenses.extend(ctx.rule.attr.applicable_licenses)
35*7594170eSAndroid Build Coastguard Worker
36*7594170eSAndroid Build Coastguard Worker    for a in dir(ctx.rule.attr):
37*7594170eSAndroid Build Coastguard Worker        # Ignore private attributes
38*7594170eSAndroid Build Coastguard Worker        if a.startswith("_"):
39*7594170eSAndroid Build Coastguard Worker            continue
40*7594170eSAndroid Build Coastguard Worker        value = getattr(ctx.rule.attr, a)
41*7594170eSAndroid Build Coastguard Worker        vlist = value if type(value) == type([]) else [value]
42*7594170eSAndroid Build Coastguard Worker        for item in vlist:
43*7594170eSAndroid Build Coastguard Worker            if type(item) == "Target" and RuleLicensedDependenciesInfo in item:
44*7594170eSAndroid Build Coastguard Worker                _maybe_expand(item, transitive_licenses)
45*7594170eSAndroid Build Coastguard Worker
46*7594170eSAndroid Build Coastguard Worker    return [
47*7594170eSAndroid Build Coastguard Worker        RuleLicensedDependenciesInfo(license_closure = depset(licenses, transitive = transitive_licenses)),
48*7594170eSAndroid Build Coastguard Worker        create_metadata_file_info(ctx),
49*7594170eSAndroid Build Coastguard Worker    ]
50*7594170eSAndroid Build Coastguard Worker
51*7594170eSAndroid Build Coastguard Workerlicense_aspect = aspect(
52*7594170eSAndroid Build Coastguard Worker    doc = """Collect transitive license closure.""",
53*7594170eSAndroid Build Coastguard Worker    implementation = _rule_licenses_aspect_impl,
54*7594170eSAndroid Build Coastguard Worker    attr_aspects = ["*"],
55*7594170eSAndroid Build Coastguard Worker    apply_to_generating_rules = True,
56*7594170eSAndroid Build Coastguard Worker    provides = [RuleLicensedDependenciesInfo, MetadataFileInfo],
57*7594170eSAndroid Build Coastguard Worker)
58*7594170eSAndroid Build Coastguard Worker
59*7594170eSAndroid Build Coastguard Worker_license_kind_template = """
60*7594170eSAndroid Build Coastguard Worker      {{
61*7594170eSAndroid Build Coastguard Worker        "target": "{kind_path}",
62*7594170eSAndroid Build Coastguard Worker        "name": "{kind_name}",
63*7594170eSAndroid Build Coastguard Worker        "conditions": {kind_conditions}
64*7594170eSAndroid Build Coastguard Worker      }}"""
65*7594170eSAndroid Build Coastguard Worker
66*7594170eSAndroid Build Coastguard Workerdef _license_kind_to_json(kind):
67*7594170eSAndroid Build Coastguard Worker    return _license_kind_template.format(kind_name = kind.name, kind_path = kind.label, kind_conditions = kind.conditions)
68*7594170eSAndroid Build Coastguard Worker
69*7594170eSAndroid Build Coastguard Workerdef _quotes_or_null(s):
70*7594170eSAndroid Build Coastguard Worker    if not s:
71*7594170eSAndroid Build Coastguard Worker        return "null"
72*7594170eSAndroid Build Coastguard Worker    return s
73*7594170eSAndroid Build Coastguard Worker
74*7594170eSAndroid Build Coastguard Workerdef _license_file(license_rule):
75*7594170eSAndroid Build Coastguard Worker    file = license_rule[LicenseInfo].license_text
76*7594170eSAndroid Build Coastguard Worker    return file if file and file.basename != "__NO_LICENSE__" else struct(path = "")
77*7594170eSAndroid Build Coastguard Worker
78*7594170eSAndroid Build Coastguard Workerdef _divine_package_name(license):
79*7594170eSAndroid Build Coastguard Worker    if license.package_name:
80*7594170eSAndroid Build Coastguard Worker        return license.package_name.removeprefix("external").removesuffix("BUILD.bazel").replace("/", " ").strip()
81*7594170eSAndroid Build Coastguard Worker    return license.rule.name.removeprefix("external_").removesuffix("_license").replace("_", " ")
82*7594170eSAndroid Build Coastguard Worker
83*7594170eSAndroid Build Coastguard Workerdef license_map(deps):
84*7594170eSAndroid Build Coastguard Worker    """Collects license to licensees map for the given set of rule targets.
85*7594170eSAndroid Build Coastguard Worker
86*7594170eSAndroid Build Coastguard Worker    TODO(asmundak): at the moment licensees lists are all empty because collecting
87*7594170eSAndroid Build Coastguard Worker    the licensees turned out to be too slow. Restore this later.
88*7594170eSAndroid Build Coastguard Worker    Args:
89*7594170eSAndroid Build Coastguard Worker        deps: list of rule targets
90*7594170eSAndroid Build Coastguard Worker    Returns:
91*7594170eSAndroid Build Coastguard Worker        dictionary mapping a license to its licensees
92*7594170eSAndroid Build Coastguard Worker    """
93*7594170eSAndroid Build Coastguard Worker    transitive_licenses = []
94*7594170eSAndroid Build Coastguard Worker    for d in deps:
95*7594170eSAndroid Build Coastguard Worker        _maybe_expand(d, transitive_licenses)
96*7594170eSAndroid Build Coastguard Worker
97*7594170eSAndroid Build Coastguard Worker    # Each rule provides the closure of its licenses, let us build the
98*7594170eSAndroid Build Coastguard Worker    # reverse map. A minor quirk is that for some reason there may be
99*7594170eSAndroid Build Coastguard Worker    # multiple license instances with with the same label. Use the
100*7594170eSAndroid Build Coastguard Worker    # intermediary dict to map rule's label to its first instance
101*7594170eSAndroid Build Coastguard Worker    license_by_label = dict()
102*7594170eSAndroid Build Coastguard Worker    licensees = dict()
103*7594170eSAndroid Build Coastguard Worker    for lic in depset(transitive = transitive_licenses).to_list():
104*7594170eSAndroid Build Coastguard Worker        if not LicenseInfo in lic:
105*7594170eSAndroid Build Coastguard Worker            continue
106*7594170eSAndroid Build Coastguard Worker        label = lic[LicenseInfo].label.name
107*7594170eSAndroid Build Coastguard Worker        if not label in license_by_label:
108*7594170eSAndroid Build Coastguard Worker            license_by_label[label] = lic
109*7594170eSAndroid Build Coastguard Worker            licensees[lic] = []
110*7594170eSAndroid Build Coastguard Worker    return licensees
111*7594170eSAndroid Build Coastguard Worker
112*7594170eSAndroid Build Coastguard Worker_license_template = """  {{
113*7594170eSAndroid Build Coastguard Worker    "rule": "{rule}",
114*7594170eSAndroid Build Coastguard Worker    "license_kinds": [{kinds}
115*7594170eSAndroid Build Coastguard Worker    ],
116*7594170eSAndroid Build Coastguard Worker    "copyright_notice": "{copyright_notice}",
117*7594170eSAndroid Build Coastguard Worker    "package_name": "{package_name}",
118*7594170eSAndroid Build Coastguard Worker    "package_url": {package_url},
119*7594170eSAndroid Build Coastguard Worker    "package_version": {package_version},
120*7594170eSAndroid Build Coastguard Worker    "license_text": "{license_text}",
121*7594170eSAndroid Build Coastguard Worker    "licensees": [
122*7594170eSAndroid Build Coastguard Worker        "{licensees}"
123*7594170eSAndroid Build Coastguard Worker    ]
124*7594170eSAndroid Build Coastguard Worker    \n  }}"""
125*7594170eSAndroid Build Coastguard Worker
126*7594170eSAndroid Build Coastguard Workerdef _used_license_to_json(license_rule, licensed_rules):
127*7594170eSAndroid Build Coastguard Worker    license = license_rule[LicenseInfo]
128*7594170eSAndroid Build Coastguard Worker    return _license_template.format(
129*7594170eSAndroid Build Coastguard Worker        rule = license.label.name,
130*7594170eSAndroid Build Coastguard Worker        copyright_notice = license.copyright_notice,
131*7594170eSAndroid Build Coastguard Worker        package_name = _divine_package_name(license),
132*7594170eSAndroid Build Coastguard Worker        package_url = _quotes_or_null(license.package_url),
133*7594170eSAndroid Build Coastguard Worker        package_version = _quotes_or_null(license.package_version),
134*7594170eSAndroid Build Coastguard Worker        license_text = _license_file(license_rule).path,
135*7594170eSAndroid Build Coastguard Worker        kinds = ",\n".join([_license_kind_to_json(kind) for kind in license.license_kinds]),
136*7594170eSAndroid Build Coastguard Worker        licensees = "\",\n        \"".join([r for r in licensed_rules]),
137*7594170eSAndroid Build Coastguard Worker    )
138*7594170eSAndroid Build Coastguard Worker
139*7594170eSAndroid Build Coastguard Workerdef license_map_to_json(licensees):
140*7594170eSAndroid Build Coastguard Worker    """Returns an array of JSON representations of a license and its licensees. """
141*7594170eSAndroid Build Coastguard Worker    return [_used_license_to_json(lic, rules) for lic, rules in licensees.items()]
142*7594170eSAndroid Build Coastguard Worker
143*7594170eSAndroid Build Coastguard Workerdef license_map_notice_files(licensees):
144*7594170eSAndroid Build Coastguard Worker    """Returns an array of license text files for the given licensee map.
145*7594170eSAndroid Build Coastguard Worker
146*7594170eSAndroid Build Coastguard Worker    Args:
147*7594170eSAndroid Build Coastguard Worker        licensees: dict returned by license_map() call
148*7594170eSAndroid Build Coastguard Worker    Returns:
149*7594170eSAndroid Build Coastguard Worker        the list of notice files this licensees map depends on.
150*7594170eSAndroid Build Coastguard Worker    """
151*7594170eSAndroid Build Coastguard Worker    notice_files = []
152*7594170eSAndroid Build Coastguard Worker    for lic in licensees.keys():
153*7594170eSAndroid Build Coastguard Worker        file = _license_file(lic)
154*7594170eSAndroid Build Coastguard Worker        if file.path:
155*7594170eSAndroid Build Coastguard Worker            notice_files.append(file)
156*7594170eSAndroid Build Coastguard Worker    return notice_files
157