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"""SBOM generation""" 15*f578df4fSJingwen Chen 16*f578df4fSJingwen Chenload( 17*f578df4fSJingwen Chen "@rules_license//rules:gather_metadata.bzl", 18*f578df4fSJingwen Chen "gather_metadata_info", 19*f578df4fSJingwen Chen "gather_metadata_info_and_write", 20*f578df4fSJingwen Chen "write_metadata_info", 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# This rule is proof of concept, and may not represent the final 28*f578df4fSJingwen Chen# form of a rule for compliance validation. 29*f578df4fSJingwen Chendef _generate_sbom_impl(ctx): 30*f578df4fSJingwen Chen # Gather all licenses and write information to one place 31*f578df4fSJingwen Chen 32*f578df4fSJingwen Chen licenses_file = ctx.actions.declare_file("_%s_licenses_info.json" % ctx.label.name) 33*f578df4fSJingwen Chen write_metadata_info(ctx, ctx.attr.deps, licenses_file) 34*f578df4fSJingwen Chen 35*f578df4fSJingwen Chen # Now turn the big blob of data into something consumable. 36*f578df4fSJingwen Chen inputs = [licenses_file] 37*f578df4fSJingwen Chen outputs = [ctx.outputs.out] 38*f578df4fSJingwen Chen args = ctx.actions.args() 39*f578df4fSJingwen Chen args.add("--licenses_info", licenses_file.path) 40*f578df4fSJingwen Chen args.add("--out", ctx.outputs.out.path) 41*f578df4fSJingwen Chen ctx.actions.run( 42*f578df4fSJingwen Chen mnemonic = "CreateSBOM", 43*f578df4fSJingwen Chen progress_message = "Creating SBOM for %s" % ctx.label, 44*f578df4fSJingwen Chen inputs = inputs, 45*f578df4fSJingwen Chen outputs = outputs, 46*f578df4fSJingwen Chen executable = ctx.executable._sbom_generator, 47*f578df4fSJingwen Chen arguments = [args], 48*f578df4fSJingwen Chen ) 49*f578df4fSJingwen Chen outputs.append(licenses_file) # also make the json file available. 50*f578df4fSJingwen Chen return [DefaultInfo(files = depset(outputs))] 51*f578df4fSJingwen Chen 52*f578df4fSJingwen Chen_generate_sbom = rule( 53*f578df4fSJingwen Chen implementation = _generate_sbom_impl, 54*f578df4fSJingwen Chen attrs = { 55*f578df4fSJingwen Chen "deps": attr.label_list( 56*f578df4fSJingwen Chen aspects = [gather_metadata_info], 57*f578df4fSJingwen Chen ), 58*f578df4fSJingwen Chen "out": attr.output(mandatory = True), 59*f578df4fSJingwen Chen "_sbom_generator": attr.label( 60*f578df4fSJingwen Chen default = Label("@rules_license//tools:write_sbom"), 61*f578df4fSJingwen Chen executable = True, 62*f578df4fSJingwen Chen allow_files = True, 63*f578df4fSJingwen Chen cfg = "exec", 64*f578df4fSJingwen Chen ), 65*f578df4fSJingwen Chen }, 66*f578df4fSJingwen Chen) 67*f578df4fSJingwen Chen 68*f578df4fSJingwen Chendef generate_sbom(**kwargs): 69*f578df4fSJingwen Chen _generate_sbom(**kwargs) 70*f578df4fSJingwen Chen 71*f578df4fSJingwen Chendef _manifest_impl(ctx): 72*f578df4fSJingwen Chen # Gather all licenses and make it available as deps for downstream rules 73*f578df4fSJingwen Chen # Additionally write the list of license filenames to a file that can 74*f578df4fSJingwen Chen # also be used as an input to downstream rules. 75*f578df4fSJingwen Chen licenses_file = ctx.actions.declare_file(ctx.attr.out.name) 76*f578df4fSJingwen Chen mappings = get_licenses_mapping(ctx.attr.deps, ctx.attr.warn_on_legacy_licenses) 77*f578df4fSJingwen Chen ctx.actions.write( 78*f578df4fSJingwen Chen output = licenses_file, 79*f578df4fSJingwen Chen content = "\n".join([",".join([f.path, p]) for (f, p) in mappings.items()]), 80*f578df4fSJingwen Chen ) 81*f578df4fSJingwen Chen return [DefaultInfo(files = depset(mappings.keys()))] 82*f578df4fSJingwen Chen 83*f578df4fSJingwen Chen_manifest = rule( 84*f578df4fSJingwen Chen implementation = _manifest_impl, 85*f578df4fSJingwen Chen doc = """Internal tmplementation method for manifest().""", 86*f578df4fSJingwen Chen attrs = { 87*f578df4fSJingwen Chen "deps": attr.label_list( 88*f578df4fSJingwen Chen doc = """List of targets to collect license files for.""", 89*f578df4fSJingwen Chen aspects = [gather_metadata_info], 90*f578df4fSJingwen Chen ), 91*f578df4fSJingwen Chen "out": attr.output( 92*f578df4fSJingwen Chen doc = """Output file.""", 93*f578df4fSJingwen Chen mandatory = True, 94*f578df4fSJingwen Chen ), 95*f578df4fSJingwen Chen "warn_on_legacy_licenses": attr.bool(default = False), 96*f578df4fSJingwen Chen }, 97*f578df4fSJingwen Chen) 98*f578df4fSJingwen Chen 99*f578df4fSJingwen Chendef manifest(name, deps, out = None, **kwargs): 100*f578df4fSJingwen Chen if not out: 101*f578df4fSJingwen Chen out = name + ".manifest" 102*f578df4fSJingwen Chen 103*f578df4fSJingwen Chen _manifest(name = name, deps = deps, out = out, **kwargs) 104*f578df4fSJingwen Chen 105*f578df4fSJingwen Chendef get_licenses_mapping(deps, warn = False): 106*f578df4fSJingwen Chen """Creates list of entries representing all licenses for the deps. 107*f578df4fSJingwen Chen 108*f578df4fSJingwen Chen Args: 109*f578df4fSJingwen Chen 110*f578df4fSJingwen Chen deps: a list of deps which should have TransitiveLicensesInfo providers. 111*f578df4fSJingwen Chen This requires that you have run the gather_licenses_info 112*f578df4fSJingwen Chen aspect over them 113*f578df4fSJingwen Chen 114*f578df4fSJingwen Chen warn: boolean, if true, display output about legacy targets that need 115*f578df4fSJingwen Chen update 116*f578df4fSJingwen Chen 117*f578df4fSJingwen Chen Returns: 118*f578df4fSJingwen Chen {File:package_name} 119*f578df4fSJingwen Chen """ 120*f578df4fSJingwen Chen tls = [] 121*f578df4fSJingwen Chen for dep in deps: 122*f578df4fSJingwen Chen lds = dep[TransitiveLicensesInfo].licenses 123*f578df4fSJingwen Chen tls.append(lds) 124*f578df4fSJingwen Chen 125*f578df4fSJingwen Chen ds = depset(transitive = tls) 126*f578df4fSJingwen Chen 127*f578df4fSJingwen Chen # Ignore any legacy licenses that may be in the report 128*f578df4fSJingwen Chen mappings = {} 129*f578df4fSJingwen Chen for lic in ds.to_list(): 130*f578df4fSJingwen Chen if type(lic.license_text) == "File": 131*f578df4fSJingwen Chen mappings[lic.license_text] = lic.package_name 132*f578df4fSJingwen Chen elif warn: 133*f578df4fSJingwen Chen # buildifier: disable=print 134*f578df4fSJingwen Chen print("Legacy license %s not included, rule needs updating" % lic.license_text) 135*f578df4fSJingwen Chen 136*f578df4fSJingwen Chen return mappings 137