1#!/usr/bin/env python3 2# Copyright 2020 Google LLC 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""Proof of concept license checker. 17 18This is only a demonstration. It will be replaced with other tools. 19""" 20 21import argparse 22import codecs 23import datetime 24import json 25import os 26 27 28TOOL = 'https//github.com/bazelbuild/rules_license/tools:write_sbom' 29 30def _load_package_data(package_info): 31 with codecs.open(package_info, encoding='utf-8') as inp: 32 return json.loads(inp.read()) 33 34def _write_sbom_header(out, package): 35 header = [ 36 'SPDXVersion: SPDX-2.2', 37 'DataLicense: CC0-1.0', 38 'SPDXID: SPDXRef-DOCUMENT', 39 'DocumentName: %s' % package, 40 # TBD 41 # 'DocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3 42 'Creator: Person: %s' % os.getlogin(), 43 'Creator: Tool: %s' % TOOL, 44 datetime.datetime.utcnow().strftime('Created: %Y-%m-%d-%H:%M:%SZ'), 45 '', 46 '##### Package: %s' % package, 47 ] 48 out.write('\n'.join(header)) 49 50 51 52def _write_sbom(out, packages): 53 """Produce a basic SBOM 54 55 Args: 56 out: file object to write to 57 packages: package metadata. A big blob of JSON. 58 """ 59 for p in packages: 60 name = p.get('package_name') or '<unknown>' 61 out.write('\n') 62 out.write('SPDXID: "%s"\n' % name) 63 out.write(' name: "%s"\n' % name) 64 if p.get('package_version'): 65 out.write(' versionInfo: "%s"\n' % p['package_version']) 66 # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. 67 cn = p.get('copyright_notice') 68 if cn: 69 out.write(' copyrightText: "%s"\n' % cn) 70 kinds = p.get('license_kinds') 71 if kinds: 72 out.write(' licenseDeclared: "%s"\n' % 73 ','.join([k['name'] for k in kinds])) 74 url = p.get('package_url') 75 if url: 76 out.write(' downloadLocation: %s\n' % url) 77 78 79def main(): 80 parser = argparse.ArgumentParser( 81 description='Demonstraton license compliance checker') 82 83 parser.add_argument('--licenses_info', 84 help='path to JSON file containing all license data') 85 parser.add_argument('--out', default='sbom.out', help='SBOM output') 86 args = parser.parse_args() 87 88 license_data = _load_package_data(args.licenses_info) 89 target = license_data[0] # we assume only one target for the demo 90 91 top_level_target = target['top_level_target'] 92 dependencies = target['dependencies'] 93 # It's not really packages, but this is close proxy for now 94 licenses = target['licenses'] 95 package_infos = target['packages'] 96 97 # These are similar dicts, so merge them by package. This is not 98 # strictly true, as different licenese can appear in the same 99 # package, but it is good enough for demonstrating the sbom. 100 101 all = {x['bazel_package']: x for x in licenses} 102 for pi in package_infos: 103 p = all.get(pi['bazel_package']) 104 if p: 105 p.update(pi) 106 else: 107 all[pi['bazel_package']] = pi 108 109 err = 0 110 with codecs.open(args.out, mode='w', encoding='utf-8') as out: 111 _write_sbom_header(out, package=top_level_target) 112 _write_sbom(out, all.values()) 113 return err 114 115 116if __name__ == '__main__': 117 main() 118