1# Copyright 2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""SBOM generation""" 15 16load( 17 "@rules_license//rules:gather_metadata.bzl", 18 "gather_metadata_info", 19 "gather_metadata_info_and_write", 20 "write_metadata_info", 21) 22load( 23 "@rules_license//rules/private:gathering_providers.bzl", 24 "TransitiveLicensesInfo", 25) 26 27# This rule is proof of concept, and may not represent the final 28# form of a rule for compliance validation. 29def _generate_sbom_impl(ctx): 30 # Gather all licenses and write information to one place 31 32 licenses_file = ctx.actions.declare_file("_%s_licenses_info.json" % ctx.label.name) 33 write_metadata_info(ctx, ctx.attr.deps, licenses_file) 34 35 # Now turn the big blob of data into something consumable. 36 inputs = [licenses_file] 37 outputs = [ctx.outputs.out] 38 args = ctx.actions.args() 39 args.add("--licenses_info", licenses_file.path) 40 args.add("--out", ctx.outputs.out.path) 41 ctx.actions.run( 42 mnemonic = "CreateSBOM", 43 progress_message = "Creating SBOM for %s" % ctx.label, 44 inputs = inputs, 45 outputs = outputs, 46 executable = ctx.executable._sbom_generator, 47 arguments = [args], 48 ) 49 outputs.append(licenses_file) # also make the json file available. 50 return [DefaultInfo(files = depset(outputs))] 51 52_generate_sbom = rule( 53 implementation = _generate_sbom_impl, 54 attrs = { 55 "deps": attr.label_list( 56 aspects = [gather_metadata_info], 57 ), 58 "out": attr.output(mandatory = True), 59 "_sbom_generator": attr.label( 60 default = Label("@rules_license//tools:write_sbom"), 61 executable = True, 62 allow_files = True, 63 cfg = "exec", 64 ), 65 }, 66) 67 68def generate_sbom(**kwargs): 69 _generate_sbom(**kwargs) 70 71def _manifest_impl(ctx): 72 # Gather all licenses and make it available as deps for downstream rules 73 # Additionally write the list of license filenames to a file that can 74 # also be used as an input to downstream rules. 75 licenses_file = ctx.actions.declare_file(ctx.attr.out.name) 76 mappings = get_licenses_mapping(ctx.attr.deps, ctx.attr.warn_on_legacy_licenses) 77 ctx.actions.write( 78 output = licenses_file, 79 content = "\n".join([",".join([f.path, p]) for (f, p) in mappings.items()]), 80 ) 81 return [DefaultInfo(files = depset(mappings.keys()))] 82 83_manifest = rule( 84 implementation = _manifest_impl, 85 doc = """Internal tmplementation method for manifest().""", 86 attrs = { 87 "deps": attr.label_list( 88 doc = """List of targets to collect license files for.""", 89 aspects = [gather_metadata_info], 90 ), 91 "out": attr.output( 92 doc = """Output file.""", 93 mandatory = True, 94 ), 95 "warn_on_legacy_licenses": attr.bool(default = False), 96 }, 97) 98 99def manifest(name, deps, out = None, **kwargs): 100 if not out: 101 out = name + ".manifest" 102 103 _manifest(name = name, deps = deps, out = out, **kwargs) 104 105def get_licenses_mapping(deps, warn = False): 106 """Creates list of entries representing all licenses for the deps. 107 108 Args: 109 110 deps: a list of deps which should have TransitiveLicensesInfo providers. 111 This requires that you have run the gather_licenses_info 112 aspect over them 113 114 warn: boolean, if true, display output about legacy targets that need 115 update 116 117 Returns: 118 {File:package_name} 119 """ 120 tls = [] 121 for dep in deps: 122 lds = dep[TransitiveLicensesInfo].licenses 123 tls.append(lds) 124 125 ds = depset(transitive = tls) 126 127 # Ignore any legacy licenses that may be in the report 128 mappings = {} 129 for lic in ds.to_list(): 130 if type(lic.license_text) == "File": 131 mappings[lic.license_text] = lic.package_name 132 elif warn: 133 # buildifier: disable=print 134 print("Legacy license %s not included, rule needs updating" % lic.license_text) 135 136 return mappings 137