• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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