#!/usr/bin/env python3 # 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. """Proof of concept license checker. This is only a demonstration. It will be replaced with other tools. """ import argparse import codecs import datetime import json import os TOOL = 'https//github.com/bazelbuild/rules_license/tools:write_sbom' def _load_package_data(package_info): with codecs.open(package_info, encoding='utf-8') as inp: return json.loads(inp.read()) def _write_sbom_header(out, package): header = [ 'SPDXVersion: SPDX-2.2', 'DataLicense: CC0-1.0', 'SPDXID: SPDXRef-DOCUMENT', 'DocumentName: %s' % package, # TBD # 'DocumentNamespace: https://swinslow.net/spdx-examples/example1/hello-v3 'Creator: Person: %s' % os.getlogin(), 'Creator: Tool: %s' % TOOL, datetime.datetime.utcnow().strftime('Created: %Y-%m-%d-%H:%M:%SZ'), '', '##### Package: %s' % package, ] out.write('\n'.join(header)) def _write_sbom(out, packages): """Produce a basic SBOM Args: out: file object to write to packages: package metadata. A big blob of JSON. """ for p in packages: name = p.get('package_name') or '' out.write('\n') out.write('SPDXID: "%s"\n' % name) out.write(' name: "%s"\n' % name) if p.get('package_version'): out.write(' versionInfo: "%s"\n' % p['package_version']) # IGNORE_COPYRIGHT: Not a copyright notice. It is a variable holding one. cn = p.get('copyright_notice') if cn: out.write(' copyrightText: "%s"\n' % cn) kinds = p.get('license_kinds') if kinds: out.write(' licenseDeclared: "%s"\n' % ','.join([k['name'] for k in kinds])) url = p.get('package_url') if url: out.write(' downloadLocation: %s\n' % url) def main(): parser = argparse.ArgumentParser( description='Demonstraton license compliance checker') parser.add_argument('--licenses_info', help='path to JSON file containing all license data') parser.add_argument('--out', default='sbom.out', help='SBOM output') args = parser.parse_args() license_data = _load_package_data(args.licenses_info) target = license_data[0] # we assume only one target for the demo top_level_target = target['top_level_target'] dependencies = target['dependencies'] # It's not really packages, but this is close proxy for now licenses = target['licenses'] package_infos = target['packages'] # These are similar dicts, so merge them by package. This is not # strictly true, as different licenese can appear in the same # package, but it is good enough for demonstrating the sbom. all = {x['bazel_package']: x for x in licenses} for pi in package_infos: p = all.get(pi['bazel_package']) if p: p.update(pi) else: all[pi['bazel_package']] = pi err = 0 with codecs.open(args.out, mode='w', encoding='utf-8') as out: _write_sbom_header(out, package=top_level_target) _write_sbom(out, all.values()) return err if __name__ == '__main__': main()