aboutsummaryrefslogtreecommitdiff
path: root/rules/gather_licenses_info.bzl
blob: bd8c210b44a523919f3af6ca5f04c38df86afa75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# Copyright 2020 Google LLC
#
# 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
#
# https://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.

"""Rules and macros for collecting LicenseInfo providers."""

load(
    "@rules_license//rules:providers.bzl",
    "LicenseInfo",
    "LicensesInfo",
)

# Debugging verbosity
_VERBOSITY = 0

def _debug(loglevel, msg):
    if _VERBOSITY > loglevel:
        print(msg)  # buildifier: disable=print

def _get_transitive_licenses(deps, licenses, trans):
    for dep in deps:
        if LicenseInfo in dep:
            license = dep[LicenseInfo]
            _debug(1, "  depends on license: %s" % license.rule)
            licenses.append(license)
        if LicensesInfo in dep:
            license_list = dep[LicensesInfo].licenses
            if license_list:
                _debug(1, "  transitively depends on: %s" % licenses)
                trans.append(license_list)

def _gather_licenses_info_impl(target, ctx):
    licenses = []
    trans = []
    if hasattr(ctx.rule.attr, "applicable_licenses"):
        _get_transitive_licenses(ctx.rule.attr.applicable_licenses, licenses, trans)
    if hasattr(ctx.rule.attr, "deps"):
        _get_transitive_licenses(ctx.rule.attr.deps, licenses, trans)
    if hasattr(ctx.rule.attr, "srcs"):
        _get_transitive_licenses(ctx.rule.attr.srcs, licenses, trans)
    return [LicensesInfo(licenses = depset(tuple(licenses), transitive = trans))]

gather_licenses_info = aspect(
    doc = """Collects LicenseInfo providers into a single LicensesInfo provider.""",
    implementation = _gather_licenses_info_impl,
    attr_aspects = ["applicable_licenses", "deps", "srcs"],
    apply_to_generating_rules = True,
)

def _quotes_or_null(s):
  if not s:
    return "null"
  return '"%s"' % s

def write_licenses_info(ctx, deps, json_out):
    """Writes LicensesInfo providers for a set of targets as JSON.

    TODO(aiuto): Document JSON schema.

    Usage:
      write_licenses_info must be called from a rule implementation, where the
      rule has run the gather_licenses_info aspect on its deps to collect the
      transitive closure of LicenseInfo providers into a LicenseInfo provider.

      foo = rule(
        implementation = _foo_impl,
        attrs = {
           "deps": attr.label_list(aspects = [gather_licenses_info])
        }
      )

      def _foo_impl(ctx):
        ...
        out = ctx.actions.declare_file("%s_licenses.json" % ctx.label.name)
        write_licenses_info(ctx, ctx.attr.deps, licenses_file)

    Args:
      ctx: context of the caller
      deps: a list of deps which should have LicensesInfo providers.
            This requires that you have run the gather_licenses_info
            aspect over them
      json_out: output handle to write the JSON info
    """

    rule_template = """  {{
    "rule": "{rule}",
    "license_kinds": [{kinds}
    ],
    "copyright_notice": "{copyright_notice}",
    "package_name": "{package_name}",
    "package_url": {package_url},
    "package_version": {package_version},
    "license_text": "{license_text}"\n  }}"""

    kind_template = """
      {{
        "target": "{kind_path}",
        "name": "{kind_name}",
        "conditions": {kind_conditions}
      }}"""

    licenses = []
    for dep in deps:
        if LicensesInfo in dep:
            for license in dep[LicensesInfo].licenses.to_list():
                _debug(0, "  Requires license: %s" % license)
                kinds = []
                for kind in license.license_kinds:
                    kinds.append(kind_template.format(
                        kind_name = kind.name,
                        kind_path = kind.label,
                        kind_conditions = kind.conditions,
                    ))
                licenses.append(rule_template.format(
                    rule = license.rule,
                    copyright_notice = license.copyright_notice,
                    package_name = license.package_name,
                    package_url = _quotes_or_null(license.package_url),
                    package_version = _quotes_or_null(license.package_version),
                    license_text = license.license_text.path,
                    kinds = ",\n".join(kinds),
                ))
    ctx.actions.write(
        output = json_out,
        content = "[\n%s\n]\n" % ",\n".join(licenses),
    )