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